From 6d4cd91bcc9588e52f1517f03377cb648baa71ca Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 11 Jul 2026 18:42:05 -0500 Subject: [PATCH] deploy: split build into docker-compose.dev.yml overlay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_0114TpmrNW4wNXsHq5CR72jQ --- README.md | 24 ++++++++++++------------ docker-compose.dev.yml | 24 ++++++++++++++++++++++++ docker-compose.yml | 20 +++++++++----------- 3 files changed, 45 insertions(+), 23 deletions(-) create mode 100644 docker-compose.dev.yml diff --git a/README.md b/README.md index d52bcee..5f064b0 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..5084dd7 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,24 @@ +# Development overlay — build the app + bot images locally instead of pulling the +# prebuilt ones from the Gitea registry. +# +# The base docker-compose.yml is production-shaped (image: only, no build:), so a +# production host can never accidentally build — it only pulls. Use this overlay +# EXPLICITLY for local work (it is not auto-loaded like docker-compose.override.yml +# would be): +# +# docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build +# +# Production stays: +# +# docker compose pull && docker compose up -d +# +# The `image:` tags inherited from the base file double as the local build tags, +# so a built image and a pulled one are interchangeable. +services: + app: + build: . + + bot: + build: + context: . + dockerfile: bot/Dockerfile diff --git a/docker-compose.yml b/docker-compose.yml index da4f146..48a3a0d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,14 +21,14 @@ services: # - "3306:3306" app: - # Pull the prebuilt image from the Gitea registry (published by - # .gitea/workflows/build-images.yml on every merge to main). `build:` is kept - # so a local `docker compose build`/`up --build` still works; on the server, - # `docker compose pull && up -d` uses the registry image and never builds. - # IMAGE_TAG defaults to `latest`; pin a specific build for reproducible - # deploys / rollback, e.g. IMAGE_TAG=sha-042a151 (see .env / .env.example). + # Prebuilt image from the Gitea registry (published by + # .gitea/workflows/build-images.yml on every merge to main). This file is + # production-shaped — image only, NO build: — so a production host can only + # ever pull, never accidentally build. IMAGE_TAG defaults to `latest`; pin a + # specific build for a reproducible deploy / rollback, e.g. + # IMAGE_TAG=sha-042a151 (see .env / .env.example). To build locally instead, + # overlay docker-compose.dev.yml (see README). image: gitea.whitlocktech.com/uom/website-app:${IMAGE_TAG:-latest} - build: . restart: unless-stopped env_file: .env environment: @@ -51,11 +51,9 @@ services: - "3000:3000" bot: - # Same as app: pull the prebuilt bot image; IMAGE_TAG pins the build. + # Same as app: prebuilt bot image, pulled in production. Build locally via + # docker-compose.dev.yml. image: gitea.whitlocktech.com/uom/website-bot:${IMAGE_TAG:-latest} - build: - context: . - dockerfile: bot/Dockerfile restart: unless-stopped env_file: .env environment: