Skip to content

fix(frontend): keep the config template outside the web root so a restart can't crash-loop - #1400

Merged
iammukeshm merged 3 commits into
fullstackhero:mainfrom
marcelo-maciel:fix/frontend-entrypoint-restart
Sep 28, 2026
Merged

iammukeshm merged 3 commits into
fullstackhero:mainfrom
marcelo-maciel:fix/frontend-entrypoint-restart

Conversation

@marcelo-maciel

@marcelo-maciel marcelo-maciel commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1399.

The admin and dashboard entrypoints deleted config.json.template after rendering config.json from it, so that the template would not be served. The container's writable layer survives a restart, which means the second start found no template, and set -e turned that into a permanent crash loop.

The template now lives at /etc/fsh/config.json.template, outside the nginx root (/usr/share/nginx/html). It cannot be served from there, so nothing needs deleting, and every start re-renders config.json from it. Rendering on each start is idempotent. It is also what a restart should do anyway.

Four files change (+8/-10): the two Dockerfiles copy the template to the new path, and the two entrypoints read it from there and no longer rm it. The admin files keep their WHY-comments, updated to stay true. Nothing else in this repo references the old path: nginx.conf, compose, Terraform (which ships config.json through S3), dokploy and the AppHost do not touch it.

Two follow-ups from review are also on this branch:

  • LF for the image files (fcf18d30). config.json.template and nginx.conf fell under * text=auto, so an image built from a Windows checkout served config.json with CRLF line endings. One .gitattributes rule (clients/*/docker/** text eol=lf) now covers every file copied into the front-end images. The index was already LF, so no file content changes.
  • A CI job that builds and restarts the images (804ab79e). No workflow built or ran the nginx images, which is how this bug shipped: the first start worked and every restart crash-looped. The new Container (admin|dashboard) job in Frontend CI builds each image and starts it. It then checks the rendered config.json with jq and confirms the template is not served. Last, it runs docker restart and repeats the checks. The Frontend CI gate now depends on it.

Verification

I built both images from this branch and ran them with --restart unless-stopped, the same way the compose stack does. All 20 steps passed:

  • Fresh start: the container is running and GET /config.json returns the injected apiBase, defaultTenant and (admin) dashboardUrl, parsed as JSON rather than eyeballed.
  • Template not served: GET /config.json.template returns the SPA's index.html fallback, with no ${FSH_API_URL} placeholder anywhere in the body.
  • docker restart: the container is still running with a restart count of 0, and config.json still has the right values.
  • docker stop + docker start: same result.
  • Missing required variable: leaving out FSH_API_URL, or FSH_DASHBOARD_URL for admin, still fails fast with exit code 2 and the existing is required message.
  • Baseline on main: the same restart step fails on images built from main. Both go to exit 1 with can't open /usr/share/nginx/html/config.json.template: no such file, so the check is capable of going red.

I also ran the new CI step locally, extracting the step verbatim from the workflow. It passes on this branch for both apps. On images built from main it fails at the right point: the first check passes, and the check after docker restart fails. On this branch, built from a Windows checkout, the served config.json has 0 CR characters. The same template on main has 5 (admin) and 4 (dashboard).

It merges cleanly with #1383, which edits the admin entrypoint and template to add FSH_DEFAULT_LANGUAGE. I checked with git merge-tree: the merged entrypoint keeps that default and reads the template from the new path. #1384 does not touch these files.

Docs: fullstackhero/docs#253 updates the entrypoint snippet in Local development and adds the changelog entry.

…tart can't crash-loop

The entrypoints deleted config.json.template after rendering it, but the
container's writable layer survives docker restart, daemon restarts and
reboots, so the next start found no template and set -e exited 1 forever.
Keeping the template in /etc/fsh means it is never served and every start
re-renders config.json idempotently.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

config.json.template and nginx.conf fell under text=auto, so an image built
from a Windows checkout served config.json with CRLF line endings.
Nothing built or ran the nginx images, which is how an entrypoint that
deleted its own template shipped: the first start worked and every restart
crash-looped. The new job starts each image, checks the rendered
config.json, confirms the template is not served, restarts, and checks again.

@iammukeshm iammukeshm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Right fix: the template belongs outside the web root, and re-rendering on every start is what a restart should do. The container smoke job closes the gap that let this ship (nothing built or restarted the nginx images). The LF rule is a good catch too. Thanks, Marcelo.

@iammukeshm
iammukeshm merged commit 6983bff into fullstackhero:main Sep 28, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Admin and dashboard containers crash-loop after their first restart

2 participants