From 68f0f490b91b95d181f6c294e83b53074938399d Mon Sep 17 00:00:00 2001 From: Arnaud Date: Sat, 11 Jul 2026 20:25:38 +0200 Subject: [PATCH] chore: move docker/ compose files and infra to repo root Moved compose.yml, compose.prod.yml, DEPLOY.md, traefik/, and cron/ out of docker/ into the repo root (docker/ removed). Updated every reference: - Dockerfile: COPY paths for the cron stage - .dockerignore: dropped the now-unneeded docker/!docker-cron exclude+exception pair (cron/ is just a normal top-level dir now, no special-casing needed) - compose.prod.yml: build context ".." -> "." (Dockerfile and compose files are now siblings, not one level apart) - CLAUDE.md, DEPLOY.md, compose.yml, traefik/epicure.yml, cron/run-digest.sh, weekly-digest route.ts: path references in comments/ docs Verified: both compose files validate, and all three Dockerfile targets (runner/cron/migrator) build clean via a local --no-cache docker build from the new layout. Co-Authored-By: Claude Sonnet 5 --- .dockerignore | 2 -- CLAUDE.md | 2 +- docker/DEPLOY.md => DEPLOY.md | 6 +++--- Dockerfile | 4 ++-- apps/web/app/api/internal/cron/weekly-digest/route.ts | 2 +- docker/compose.prod.yml => compose.prod.yml | 8 ++++---- docker/compose.yml => compose.yml | 2 +- {docker/cron => cron}/crontab | 0 {docker/cron => cron}/run-digest.sh | 2 +- {docker/traefik => traefik}/epicure.yml | 2 +- 10 files changed, 14 insertions(+), 16 deletions(-) rename docker/DEPLOY.md => DEPLOY.md (93%) rename docker/compose.prod.yml => compose.prod.yml (97%) rename docker/compose.yml => compose.yml (95%) rename {docker/cron => cron}/crontab (100%) rename {docker/cron => cron}/run-digest.sh (87%) rename {docker/traefik => traefik}/epicure.yml (95%) diff --git a/.dockerignore b/.dockerignore index 6e57717..72cc594 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,6 +5,4 @@ .git .env* !.env.example -docker -!docker/cron *.md diff --git a/CLAUDE.md b/CLAUDE.md index bec379e..02f2382 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,7 +7,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ```bash cp .env.example .env.local # fill in secrets (lives at repo root) ln -s ../../.env.local apps/web/.env.local # Next.js reads from apps/web/, not root -docker compose -f docker/compose.yml up -d # start postgres, redis, minio +docker compose -f compose.yml up -d # start postgres, redis, minio pnpm install pnpm db:generate # generate migrations from schema (first time) pnpm db:migrate # apply migrations diff --git a/docker/DEPLOY.md b/DEPLOY.md similarity index 93% rename from docker/DEPLOY.md rename to DEPLOY.md index bcac781..84b61d4 100644 --- a/docker/DEPLOY.md +++ b/DEPLOY.md @@ -4,7 +4,7 @@ 1. Stacks → Add stack → **Repository** 2. Repository URL: this repo. Reference: branch to track (e.g. `main`) -3. Compose path: `docker/compose.prod.yml` +3. Compose path: `compose.prod.yml` 4. Environment variables (Portainer stack env, not committed): ``` @@ -47,7 +47,7 @@ ANTHROPIC_API_KEY= OLLAMA_BASE_URL= ``` -Every var listed here must exist in `docker/compose.prod.yml`'s `web.environment` block to actually +Every var listed here must exist in `compose.prod.yml`'s `web.environment` block to actually reach the container — Portainer stack env alone isn't enough, it only fills in `${...}` refs the compose file declares. @@ -70,7 +70,7 @@ Both are idempotent — safe to re-run on every stack redeploy, not just the fir ## Traefik (separate LXC, file provider) -1. Copy `docker/traefik/epicure.yml` into the traefik LXC's dynamic config directory. +1. Copy `traefik/epicure.yml` into the traefik LXC's dynamic config directory. 2. Replace `HOST_DOMAIN` with the public hostname and `PORTAINER_LXC_IP` with the portainer LXC's network IP (must match `WEB_PORT` published in compose.prod.yml). 3. Confirm `certResolver` name matches what's set in traefik's static config. 4. Traefik picks it up automatically (file provider watches for changes) — no restart needed. diff --git a/Dockerfile b/Dockerfile index 39ce4de..83cbf76 100644 --- a/Dockerfile +++ b/Dockerfile @@ -63,7 +63,7 @@ CMD ["node", "apps/web/server.js"] # rather than `runner`, keeping the image tiny and independent of the Next build. FROM base AS cron RUN apk add --no-cache curl -COPY docker/cron/crontab /etc/crontabs/root -COPY docker/cron/run-digest.sh /usr/local/bin/run-digest.sh +COPY cron/crontab /etc/crontabs/root +COPY cron/run-digest.sh /usr/local/bin/run-digest.sh RUN chmod +x /usr/local/bin/run-digest.sh CMD ["crond", "-f", "-l", "2"] diff --git a/apps/web/app/api/internal/cron/weekly-digest/route.ts b/apps/web/app/api/internal/cron/weekly-digest/route.ts index bf55d6a..f5b8116 100644 --- a/apps/web/app/api/internal/cron/weekly-digest/route.ts +++ b/apps/web/app/api/internal/cron/weekly-digest/route.ts @@ -18,7 +18,7 @@ import { import { sendEmail, weeklyDigestHtml } from "@/lib/email"; // Internal cron endpoint — triggered by the `digest-cron` container on a weekly -// schedule (see docker/compose.prod.yml). Not part of the public API surface; +// schedule (see compose.prod.yml). Not part of the public API surface; // protected by a shared secret rather than user auth. // // Computes, for every user: new followers / new comments / new ratings on diff --git a/docker/compose.prod.yml b/compose.prod.yml similarity index 97% rename from docker/compose.prod.yml rename to compose.prod.yml index 27cfc28..28a9e7e 100644 --- a/docker/compose.prod.yml +++ b/compose.prod.yml @@ -51,7 +51,7 @@ services: migrate: build: - context: .. + context: . dockerfile: Dockerfile target: migrator environment: @@ -63,7 +63,7 @@ services: web: build: - context: .. + context: . dockerfile: Dockerfile # Explicit target: the Dockerfile's last stage is no longer `runner` now # that `cron` was appended after it, and the default build target is @@ -124,11 +124,11 @@ services: condition: service_healthy # Fires the weekly digest email send by POSTing to `web`'s internal cron - # route on a schedule (see docker/cron/crontab). Tiny alpine+curl+crond + # route on a schedule (see cron/crontab). Tiny alpine+curl+crond # image — not part of the app build, just a periodic HTTP trigger. digest-cron: build: - context: .. + context: . dockerfile: Dockerfile target: cron restart: always diff --git a/docker/compose.yml b/compose.yml similarity index 95% rename from docker/compose.yml rename to compose.yml index efd8e5f..681d51e 100644 --- a/docker/compose.yml +++ b/compose.yml @@ -1,5 +1,5 @@ # Dev-only infra: postgres/redis/minio. The app and the `digest-cron` container -# (docker/compose.prod.yml) aren't run here — use `pnpm dev` locally, and hit +# (compose.prod.yml) aren't run here — use `pnpm dev` locally, and hit # POST /api/internal/cron/weekly-digest with the CRON_SECRET header directly if # you need to test the weekly digest send without waiting for the schedule. services: diff --git a/docker/cron/crontab b/cron/crontab similarity index 100% rename from docker/cron/crontab rename to cron/crontab diff --git a/docker/cron/run-digest.sh b/cron/run-digest.sh similarity index 87% rename from docker/cron/run-digest.sh rename to cron/run-digest.sh index 6495ceb..efa78ec 100644 --- a/docker/cron/run-digest.sh +++ b/cron/run-digest.sh @@ -1,6 +1,6 @@ #!/bin/sh # Triggers the weekly digest email send on the `web` service. Run on a schedule -# by busybox crond (see docker/cron/crontab). Fails loudly (non-zero exit, +# by busybox crond (see cron/crontab). Fails loudly (non-zero exit, # stderr) but crond just tries again next week — no retry loop here on purpose, # keep this script trivial. set -eu diff --git a/docker/traefik/epicure.yml b/traefik/epicure.yml similarity index 95% rename from docker/traefik/epicure.yml rename to traefik/epicure.yml index 11fb37d..fd80943 100644 --- a/docker/traefik/epicure.yml +++ b/traefik/epicure.yml @@ -17,7 +17,7 @@ http: epicure: loadBalancer: servers: - - url: "http://PORTAINER_LXC_IP:3000" # match WEB_PORT from docker/compose.prod.yml env + - url: "http://PORTAINER_LXC_IP:3000" # match WEB_PORT from compose.prod.yml env healthCheck: path: / interval: 10s