deploy: split build into docker-compose.dev.yml overlay

Make the base docker-compose.yml strictly production-shaped — image: only, no
build: — so a production host can only ever pull, never accidentally build
(compose gives build precedence for `up --build`/`build`, which mixed the two
modes). Local builds move to an explicit, non-auto-loaded overlay.

  Production:   docker compose pull && docker compose up -d
  Development:  docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build

Verified with `docker compose config`: base renders image-only (no build) for
both services; the dev overlay adds build back (app -> Dockerfile,
bot -> bot/Dockerfile). README quick-start updated to the two-file flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114TpmrNW4wNXsHq5CR72jQ
This commit is contained in:
2026-07-11 18:42:05 -05:00
parent 25ff5aa836
commit 6d4cd91bcc
3 changed files with 45 additions and 23 deletions

View File

@@ -92,8 +92,10 @@ UOMSITE/
### Option A — Docker Compose (full stack)
The simplest way to run everything. The image installs server deps, **builds the React client**,
and Express serves it; MariaDB runs in its own container; tables + defaults + the first admin are
`docker-compose.yml` is **production-shaped**: it *pulls* the prebuilt `app` and `bot` images from
the Gitea container registry (published by `.gitea/workflows/build-images.yml` on every merge to
`main`) — it never builds. Each image already bundles the server deps and the built React client,
which Express serves. MariaDB runs in its own container; tables + defaults + the first admin are
created automatically on first boot.
```bash
@@ -103,7 +105,9 @@ cp .env.example .env
# JWT_SECRET (a long random string)
# ADMIN_USERNAME, ADMIN_PASSWORD (your first admin login)
docker compose up -d --build
docker compose pull && docker compose up -d # IMAGE_TAG defaults to `latest`
# pin a specific build (reproducible deploy / rollback):
IMAGE_TAG=sha-042a151 docker compose pull && docker compose up -d
```
- App: **http://localhost:3000** (binds `0.0.0.0`)
@@ -111,19 +115,15 @@ docker compose up -d --build
- Logs: `docker compose logs -f app` (and `./logs/app.log` on the host)
- Stop: `docker compose down` (add `-v` to also wipe the database + uploads volumes)
**Deploy prebuilt images (no local build).** Every merge to `main` publishes the
`app` and `bot` images to the Gitea container registry
(`.gitea/workflows/build-images.yml`), so on the server you can pull instead of
building:
**Build the images locally instead of pulling** (offline, or to test an unmerged change) — overlay
the dev file, which adds `build:` back:
```bash
docker compose pull && docker compose up -d # IMAGE_TAG defaults to `latest`
# pin a specific build (reproducible deploy / rollback):
IMAGE_TAG=sha-042a151 docker compose pull && docker compose up -d
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
```
`build:` is kept in `docker-compose.yml`, so `docker compose up -d --build` still
works if you'd rather build locally.
Keeping `build:` out of the base file means a production host can only ever pull — it can never
accidentally build.
### Option B — Local development (hot reload)