Skip to content

feat(docker): default app image, standalone by docker run - #281

Merged
antosubash merged 4 commits into
mainfrom
worktree-docker-default-app
Aug 24, 2026
Merged

feat(docker): default app image, standalone by docker run#281
antosubash merged 4 commits into
mainfrom
worktree-docker-default-app

Conversation

@antosubash

@antosubashantosubash commented Aug 21, 2026

Copy link
Copy Markdown
Owner

The repo shipped docker/worker.Dockerfile (Celery) but nothing that ran the app itself — even though every smpy new scaffold gets a Dockerfile. This adds the equivalent for the reference app.

What's here

  • Dockerfile (root — so docker build . and PaaS auto-detection just work). 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-bookworm, non-root uid 10001, HEALTHCHECK on /health.
  • docker/entrypoint.shalembic 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 bare docker run -p 8000:8000 <image> works. Loud warning; set them to keep sessions across restarts.
  • A default admin. Without SM_USERS_BOOTSTRAP_* a fresh container served a login page nobody held credentials for. The entrypoint now seeds admin@example.com / changeme — the same pair .env.example uses for local dev, so the container and make dev behave 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.
  • compose app service + make docker-build / docker-app / docker-compose-app (all honour SM_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 the worker/beat services built from docker/worker.Dockerfile. Drop the flag and set SM_BG_TASKS_BROKER_URL / _RESULT_BACKEND to run tasks from the web process.

Two fixes that were load-bearing

  1. 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 in production, and the existing compose worker/beat silently used localhost instead of the redis hostname they set. Both fields now resolve from env at construction (DB hydration still wins afterwards). Covered by modules/background_tasks/tests/test_bg_settings_env.py. Still needed by worker/beat and by any app that keeps the module.
  2. worker/beat declared a required env_file: .env, which is gitignored — a fresh clone couldn't docker compose anything at all. Now required: false, matching the CLI templates.

Verification (local)

  • docker build → 768MB image; docker run (with zeroSM_BG_TASKS_* vars set) and docker compose up app both reach health=healthy with no other services running.
  • Admin seeding: admin@example.com / changeme logs in (204) on a fresh volume and a wrong password is rejected (400); explicit SM_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.ico 404 (no favicon configured).
  • Celery really is gone: /admin/background-tasks/ 404s, the settings module list and admin sidebar don't mention it, and modules.generated.ts has no background_tasks entry.
  • Assets come from the built bundle (/static/dist/..., precompressed), not a Vite dev server; anonymous /dashboard/ still 302s to login.
  • Data survives docker compose restart on the volume; migrations are not re-applied.
  • make ci-python-lint, ci-python-typecheck, ci-check-file-size clean; full uv run pytest = 2150 passed.

Note: the /health payload reports current_revision: null in this repo — resolve_head_revision swallows alembic's multiple-heads error. Pre-existing and identical in local dev, so left alone here.

https://claude.ai/code/session_01MKtkDrsfDCwZtXxbPGK3Tv

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_01MKtkDrsfDCwZtXxbPGK3Tv
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Aug 21, 2026

Copy link
Copy Markdown

Deploying simple-module-python with Cloudflare Pages Cloudflare Pages

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

View logs

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
antosubash marked this pull request as ready for review August 24, 2026 09:20
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for security reviews. Please try again later.

@antosubash
antosubash merged commit c00a5b4 into mainAug 24, 2026
13 checks passed
Sign up for freeto 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.

1 participant

@antosubash