Skip to content

Fix sitemap.xml 404 on Azure Container Apps (nginx missing explicit route)#430

Merged
aurelianware merged 3 commits intomainfrom
copilot/fix-sitemap-404-error
Mar 5, 2026
Merged

Fix sitemap.xml 404 on Azure Container Apps (nginx missing explicit route)#430
aurelianware merged 3 commits intomainfrom
copilot/fix-sitemap-404-error

Conversation

Copy link
Contributor

Copilot AI commented Mar 4, 2026

  • Add src/site/nginx.conf — custom nginx config with explicit sitemap/robots routes, try_files, and server-level security headers
  • Update src/site/Dockerfile — two-stage build:
    • Stage 1 (alpine:3.21, pinned): strips nginx.conf, Dockerfile, .dockerignore, k8s/, and .k8s/ from the web content tree so none of these are web-accessible
    • Stage 2 (nginx-unprivileged:alpine): installs config to /etc/nginx/conf.d/default.conf, copies clean content
  • Reduce static asset cache from 365d to 1d in nginx.conf (assets are not filename-fingerprinted)
Original prompt

Sitemap.xml still gives 404 after last fix for it

https://cloudhealthoffice.com/sitemap.xml


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

…tion and two-stage Dockerfile

Co-authored-by: aurelianware <194855645+aurelianware@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix 404 error for sitemap.xml file Fix sitemap.xml 404 on Azure Container Apps (nginx missing explicit route) Mar 4, 2026
@aurelianware aurelianware marked this pull request as ready for review March 5, 2026 00:02
Copilot AI review requested due to automatic review settings March 5, 2026 00:02
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.conf with explicit routes for /sitemap.xml and /robots.txt, plus try_files handling and shared security headers.
  • Refactor src/site/Dockerfile into 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.

Comment on lines 4 to +11
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
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +31
# Versioned static assets — long-lived cache
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 365d;
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
# 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;

Copilot uses AI. Check for mistakes.
WORKDIR /usr/share/nginx/html

# Stage 1 — assemble web content, removing build artefacts not meant for the web root
FROM alpine:3 AS content
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
FROM alpine:3 AS content
FROM alpine:3.21 AS content

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment on lines +5 to +6
# Remove the nginx config: it belongs in /etc/nginx, not the web root
RUN rm -f nginx.conf
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/.)

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 uses AI. Check for mistakes.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/.)

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@aurelianware
Copy link
Owner

@copilot apply changes based on the comments in this thread

Copy link
Contributor Author

Copilot AI commented Mar 5, 2026

@copilot apply changes based on the comments in this thread

All three changes from that review are already applied in commit 302a545:

  • alpine:3 pinned to alpine:3.21
  • expires 365d reduced to expires 1d for non-fingerprinted assets
  • k8s/, .k8s/, Dockerfile, and .dockerignore removed from the Stage 1 content tree so none are web-accessible

@aurelianware aurelianware merged commit b50cc7b into main Mar 5, 2026
57 checks passed
@aurelianware aurelianware deleted the copilot/fix-sitemap-404-error branch March 5, 2026 02:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants