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 <noreply@anthropic.com>
This commit is contained in:
Arnaud
2026-07-11 20:25:38 +02:00
parent 8b749f432e
commit 68f0f490b9
10 changed files with 14 additions and 16 deletions
-2
View File
@@ -5,6 +5,4 @@
.git
.env*
!.env.example
docker
!docker/cron
*.md
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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.
+2 -2
View File
@@ -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"]
@@ -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
+4 -4
View File
@@ -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
+1 -1
View File
@@ -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:
@@ -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
@@ -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