feat(docker): default app image, standalone by docker run - #281
Merged
Conversation
The repo shipped a Celery worker image but nothing that ran the app
itself, so "run the default app in Docker" meant writing a Dockerfile by
hand — even though every `smpy new` scaffold gets one.
Adds a root `Dockerfile` (the conventional place: `docker build .` and
most PaaS auto-detect it) building host + every bundled module in one
uv+Node builder stage, because the Vite build imports
`modules.generated.{ts,css}` that `gen-pages` emits from the *installed
Python modules*. Runtime is `python:3.12-slim`, non-root, healthchecked.
Standalone means standalone: SQLite under /app/data, no Postgres and no
Redis needed to boot. `docker/entrypoint.sh` applies `alembic upgrade
heads` and generates ephemeral values for the three secrets production
refuses to start without, so a bare `docker run -p 8000:8000` works.
Two fixes were load-bearing for that:
- `BackgroundTasksSettings` read neither `SM_BG_TASKS_BROKER_URL` nor
`SM_BG_TASKS_RESULT_BACKEND`, so the production validator only ever
saw the localhost defaults it rejects — no container with the module
installed could boot, and the compose worker/beat services silently
used localhost instead of the `redis` hostname they set. Both fields
now resolve from env at construction; DB hydration still wins after.
- worker/beat declared a required `env_file: .env`, which is gitignored
— a fresh clone couldn't `docker compose` anything at all, the new app
service included.
Verified locally: `docker run` and `docker compose up app` both boot
healthy with no other services, admin bootstrap + browser login work,
the built bundle hydrates (no Vite dev-server tags), precompressed
assets serve from /static, and data survives a restart on the volume.
Claude-Session: https://claude.ai/code/session_01MKtkDrsfDCwZtXxbPGK3TvDeploying simple-module-python with |
| Latest commit: | 75180d3 |
| Status: | ✅ Deploy successful! |
| Preview URL: | https://4af27c96.simple-module-python.pages.dev |
| Branch Preview URL: | https://worktree-docker-default-app.simple-module-python.pages.dev |
A background queue means a second process and a broker — the opposite of what a standalone image is for. The default image was setting SM_BG_TASKS_BROKER_URL to the compose `redis` hostname purely to satisfy the production validator, advertising a dependency it never used. The build now passes `--no-install-package simple-module-background-tasks`, so the module has no entry point to discover: no broker env, no Celery settings, no admin page, and none of its pages in the bundle. Background jobs stay where they belong — the worker/beat services built from docker/worker.Dockerfile. Verified: image boots healthy with zero SM_BG_TASKS_* vars set, login and /dashboard/ work, /admin/background-tasks/ 404s, the admin settings list and sidebar no longer mention it, and `modules.generated.ts` has no background_tasks entry. Claude-Session: https://claude.ai/code/session_01MKtkDrsfDCwZtXxbPGK3Tv
A fresh container served a login page nobody held credentials for: the users bootstrap needs both an email and a password, and unset meant no account at all. The compose file and `make docker-app` papered over it with `admin`/`admin`, which is a bad thing to bake into an image that is also meant to run on real hosts. The entrypoint now defaults `SM_USERS_BOOTSTRAP_EMAIL` to admin@example.com and, when no password is given, generates one and prints it once. Explicit values still win, and compose/make just pass the vars through instead of forcing weak ones. Safe on a reused volume by construction: the users module applies the seed only while its table is empty, so the printed password is a first-boot value and an existing account is never touched. The banner says so, and points at `smpy users create-admin --force` for recovery. Verified on a fresh volume: printed credentials log in (204), `admin`/`admin` is rejected (400), the password survives a restart unchanged, and passing both env vars logs no banner and uses them. Claude-Session: https://claude.ai/code/session_01MKtkDrsfDCwZtXxbPGK3Tv
The image previously generated a random first-boot password and printed it once. A fixed, well-known default is the better fit for a starter image you are meant to `docker run` and log straight into: no log-scraping step, and it matches the `changeme` that `.env.example` already uses for local dev, so the container and `make dev` behave the same. The seed still only lands while the users table is empty, so a public default cannot overwrite an existing account on a reused volume. The banner is loud about the tradeoff and names the two env vars that replace it. Claude-Session: https://claude.ai/code/session_01MKtkDrsfDCwZtXxbPGK3Tv
antosubash
marked this pull request as ready for review
August 24, 2026 09:20
You have reached your Codex usage limits for security reviews. Please try again later. |
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The repo shipped
docker/worker.Dockerfile(Celery) but nothing that ran the app itself — even though everysmpy newscaffold gets a Dockerfile. This adds the equivalent for the reference app.What's here
Dockerfile(root — sodocker build .and PaaS auto-detection just work). One uv+Node builder stage, because the Vite build importsmodules.generated.{ts,css}thatgen-pagesemits from the installed Python modules; runtime ispython:3.12-slim-bookworm, non-root uid 10001,HEALTHCHECKon/health.docker/entrypoint.sh—alembic upgrade heads, plus ephemeral values for the three secrets production refuses to boot without (SM_SECRET_KEY,SM_USERS_{RESET_PASSWORD,VERIFICATION}_TOKEN_SECRET), so a baredocker run -p 8000:8000 <image>works. Loud warning; set them to keep sessions across restarts.SM_USERS_BOOTSTRAP_*a fresh container served a login page nobody held credentials for. The entrypoint now seedsadmin@example.com/changeme— the same pair.env.exampleuses for local dev, so the container andmake devbehave alike — and prints a warning naming the two env vars that replace it. The users module applies the seed only while its table is empty, so the public default cannot overwrite an existing account on a reused volume.appservice +make docker-build/docker-app/docker-compose-app(all honourSM_APP_PORT), README +docs/reference/deployment.md..dockerignore: excludes.git/and.claude/— the latter holds full worktree checkouts of this repo, which multiplied the build context.Standalone means standalone: SQLite under
/app/data, no Postgres and no Redis required.No Celery in the image. The build passes
--no-install-package simple-module-background-tasks, so the module has no entry point to discover — no broker env, no admin page, none of its pages in the bundle. A queue is a second process plus a Redis, which is the opposite of a standalone image; background jobs stay with theworker/beatservices built fromdocker/worker.Dockerfile. Drop the flag and setSM_BG_TASKS_BROKER_URL/_RESULT_BACKENDto run tasks from the web process.Two fixes that were load-bearing
BackgroundTasksSettingsread neitherSM_BG_TASKS_BROKER_URLnorSM_BG_TASKS_RESULT_BACKEND, so the production validator only ever saw the localhost defaults it rejects — no container with the module installed could boot in production, and the existing composeworker/beatsilently usedlocalhostinstead of theredishostname they set. Both fields now resolve from env at construction (DB hydration still wins afterwards). Covered bymodules/background_tasks/tests/test_bg_settings_env.py. Still needed by worker/beat and by any app that keeps the module.worker/beatdeclared a requiredenv_file: .env, which is gitignored — a fresh clone couldn'tdocker composeanything at all. Nowrequired: false, matching the CLI templates.Verification (local)
docker build→ 768MB image;docker run(with zeroSM_BG_TASKS_*vars set) anddocker compose up appboth reachhealth=healthywith no other services running.admin@example.com/changemelogs in (204) on a fresh volume and a wrong password is rejected (400); explicitSM_USERS_BOOTSTRAP_*values are used instead with no warning banner (204 for them, 400 for the default); and rebooting an existing volume with no env vars re-seeds nothing — the default is still rejected (400) and the original account still works (204). Browser login works too;/dashboard/and/admin/settings/render styled, console clean apart from the pre-existing/favicon.ico404 (no favicon configured)./admin/background-tasks/404s, the settings module list and admin sidebar don't mention it, andmodules.generated.tshas nobackground_tasksentry./static/dist/..., precompressed), not a Vite dev server; anonymous/dashboard/still 302s to login.docker compose restarton the volume; migrations are not re-applied.make ci-python-lint,ci-python-typecheck,ci-check-file-sizeclean; fulluv run pytest= 2150 passed.Note: the
/healthpayload reportscurrent_revision: nullin this repo —resolve_head_revisionswallows alembic's multiple-heads error. Pre-existing and identical in local dev, so left alone here.https://claude.ai/code/session_01MKtkDrsfDCwZtXxbPGK3Tv