Fix sitemap.xml 404 on Azure Container Apps (nginx missing explicit route)#430
Fix sitemap.xml 404 on Azure Container Apps (nginx missing explicit route)#430aurelianware merged 3 commits intomainfrom
Conversation
…tion and two-stage Dockerfile Co-authored-by: aurelianware <194855645+aurelianware@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Updates the marketing site container to reliably serve /sitemap.xml (and other static assets) on Azure Container Apps by introducing an explicit NGINX server configuration and wiring it into the image build.
Changes:
- Add a custom
nginx.confwith explicit routes for/sitemap.xmland/robots.txt, plustry_fileshandling and shared security headers. - Refactor
src/site/Dockerfileinto a two-stage build that installs the NGINX config under/etc/nginx/conf.d/and removes the config from the web root content tree.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/site/nginx.conf | Defines explicit NGINX routing for sitemap/robots and static file serving on port 8080. |
| src/site/Dockerfile | Switches to multi-stage build to keep NGINX config out of the served content and install it under /etc/nginx. |
| COPY . . | ||
| # Remove the nginx config: it belongs in /etc/nginx, not the web root | ||
| RUN rm -f nginx.conf | ||
|
|
||
| # Stage 2 — serve with nginx-unprivileged (non-root, port 8080) | ||
| FROM nginxinc/nginx-unprivileged:alpine | ||
| COPY nginx.conf /etc/nginx/conf.d/default.conf | ||
| COPY --from=content /site /usr/share/nginx/html |
There was a problem hiding this comment.
The final image copies the entire build context into /usr/share/nginx/html (COPY --from=content /site ...). Even with the current .dockerignore, this will still publish build-only files like the Dockerfile, .dockerignore, and the k8s/ manifests to the public web root. Consider copying only the files/directories that should be web-accessible (or expanding .dockerignore to exclude k8s/, Dockerfile, etc.) to reduce information exposure.
src/site/nginx.conf
Outdated
| # Versioned static assets — long-lived cache | ||
| location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { | ||
| expires 365d; |
There was a problem hiding this comment.
The expires 365d cache policy applies to all .css/.js files matched by this regex, but the site assets are not filename-fingerprinted (e.g., css/sentinel.css, js/mobile-nav.js). This can cause clients to keep serving stale CSS/JS for up to a year after a deploy. Consider reducing the max-age for non-fingerprinted assets (or splitting into separate locations for truly versioned assets only).
| # Versioned static assets — long-lived cache | |
| location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { | |
| expires 365d; | |
| # Static assets — cached, but not for excessively long periods (non-fingerprinted) | |
| location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { | |
| expires 1d; |
src/site/Dockerfile
Outdated
| WORKDIR /usr/share/nginx/html | ||
|
|
||
| # Stage 1 — assemble web content, removing build artefacts not meant for the web root | ||
| FROM alpine:3 AS content |
There was a problem hiding this comment.
FROM alpine:3 is a moving tag and can change unexpectedly over time, making builds less reproducible and potentially introducing unreviewed changes. Other repo Dockerfiles pin Alpine to a specific minor version (e.g., alpine:3.21). Consider pinning the content stage base image to a specific version as well.
| FROM alpine:3 AS content | |
| FROM alpine:3.21 AS content |
src/site/Dockerfile
Outdated
| # Remove the nginx config: it belongs in /etc/nginx, not the web root | ||
| RUN rm -f nginx.conf |
There was a problem hiding this comment.
COPY . . (and then copying /site into /usr/share/nginx/html) will publish non-website artifacts like k8s/site-deployment.yaml (and the Dockerfile itself) into the web root. That leaks internal deployment details and makes these files web-accessible. Consider tightening what gets copied (explicit allowlist) and/or updating src/site/.dockerignore to exclude k8s/, Dockerfile, etc. (Note: .dockerignore currently ignores .k8s/, but the directory here is named k8s/.)
| # Remove the nginx config: it belongs in /etc/nginx, not the web root | |
| RUN rm -f nginx.conf | |
| # Remove configuration and deployment artefacts that do not belong in the web root | |
| RUN rm -f nginx.conf Dockerfile && rm -rf k8s .k8s |
There was a problem hiding this comment.
COPY . .(and then copying/siteinto/usr/share/nginx/html) will publish non-website artifacts likek8s/site-deployment.yaml(and theDockerfileitself) into the web root. That leaks internal deployment details and makes these files web-accessible. Consider tightening what gets copied (explicit allowlist) and/or updatingsrc/site/.dockerignoreto excludek8s/,Dockerfile, etc. (Note:.dockerignorecurrently ignores.k8s/, but the directory here is namedk8s/.)Suggested change
# Remove the nginx config: it belongs in /etc/nginx, not the web root RUN rm -f nginx.conf # Remove configuration and deployment artefacts that do not belong in the web root RUN rm -f nginx.conf Dockerfile && rm -rf k8s .k8s
@copilot can you address this code review comment
There was a problem hiding this comment.
Fixed in 302a545. The Stage 1 RUN now removes Dockerfile, .dockerignore, k8s/, and .k8s/ before the cleaned tree is copied into the web root — none of those files are web-accessible. Also pinned alpine:3.21 (matching other repo Dockerfiles) and reduced the asset cache from 365d to 1d since the assets aren't filename-fingerprinted.
…nore; pin alpine:3.21; reduce asset cache to 1d Co-authored-by: aurelianware <194855645+aurelianware@users.noreply.github.com>
|
@copilot apply changes based on the comments in this thread |
All three changes from that review are already applied in commit 302a545:
|
src/site/nginx.conf— custom nginx config with explicit sitemap/robots routes,try_files, and server-level security headerssrc/site/Dockerfile— two-stage build:alpine:3.21, pinned): stripsnginx.conf,Dockerfile,.dockerignore,k8s/, and.k8s/from the web content tree so none of these are web-accessiblenginx-unprivileged:alpine): installs config to/etc/nginx/conf.d/default.conf, copies clean content365dto1dinnginx.conf(assets are not filename-fingerprinted)Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.