feat(server): packaging Docker Compose et push Gitea Registry (ticket #52)

Ajoute le Dockerfile multi-stage (build dart:stable, runtime
debian:bookworm-slim avec ca-certificates, exécutable AOT compilé), le
docker-compose.yaml (API + Postgres avec healthcheck et
depends_on: service_healthy), .env.example, .dockerignore et les
scripts scripts/docker-entrypoint.sh (migration au démarrage) et
scripts/push-gitea-image.sh (aucun identifiant en dur, mot de passe
via --password-stdin). dart analyze clean, dart test 24/24 vert,
`docker compose config` valide (syntaxe, interpolation de variables,
healthcheck). Dockerfile et scripts relus manuellement et jugés
corrects ; pas de docker build/compose up réel faute d'accès au
daemon Docker dans ce sandbox.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 10:40:11 +02:00
parent 14e04784ed
commit f03dead3e4
8 changed files with 205 additions and 2 deletions

5
server/.dockerignore Normal file
View File

@ -0,0 +1,5 @@
.dart_tool/
build/
.env
*.log
pubspec_overrides.yaml

23
server/.env.example Normal file
View File

@ -0,0 +1,23 @@
# API container.
# Set API_BIND_ADDRESS to the Docker host interface reachable by the external
# reverse proxy. Use 0.0.0.0 for LAN-wide exposure, or a specific host IP to
# restrict where Docker publishes the port.
API_BIND_ADDRESS=0.0.0.0
API_PORT=8080
MIGRATE_ON_STARTUP=true
# PostgreSQL container bootstrap values. Replace the password before deploying.
POSTGRES_DB=gametime
POSTGRES_USER=gametime
POSTGRES_PASSWORD=change-me
# API database connection values. With this docker-compose.yaml, the database
# host is the Compose service name `postgres`.
DATABASE_HOST=postgres
DATABASE_PORT=5432
DATABASE_NAME=gametime
DATABASE_USER=gametime
DATABASE_PASSWORD=change-me
# External HTTPS is terminated by a reverse proxy running outside this Compose
# stack. Point that proxy to http://<docker-host>:${API_PORT}.

33
server/Dockerfile Normal file
View File

@ -0,0 +1,33 @@
FROM dart:stable AS build
WORKDIR /app
COPY pubspec.* ./
RUN dart pub get
COPY . .
RUN dart compile exe bin/server.dart -o build/server \
&& dart compile exe bin/migrate.dart -o build/migrate
FROM debian:bookworm-slim AS runtime
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
ENV PORT=8080
ENV MIGRATE_ON_STARTUP=true
COPY --from=build /app/build/server /app/bin/server
COPY --from=build /app/build/migrate /app/bin/migrate
COPY migrations/ /app/migrations/
COPY scripts/docker-entrypoint.sh /app/scripts/docker-entrypoint.sh
RUN chmod +x /app/bin/server /app/bin/migrate /app/scripts/docker-entrypoint.sh
EXPOSE 8080
ENTRYPOINT ["/app/scripts/docker-entrypoint.sh"]
CMD ["/app/bin/server"]

View File

@ -125,13 +125,73 @@ without failing the whole request. Accepting a share creates a new
`409` for revoked or already answered shares, while missing shares or recipients
return `404`.
## Docker Deployment
Ticket #52 adds Docker packaging for a headless Linux deployment. TLS is not
handled by the API container: terminate HTTPS in an external reverse proxy and
forward traffic to the Docker host on `API_PORT`.
Create a local environment file:
```bash
cp .env.example .env
```
Edit `.env` before deployment:
- `API_BIND_ADDRESS`: Docker host interface to publish. Use `0.0.0.0` for LAN
access by the external reverse proxy, or a specific host IP to restrict the
bind address.
- `API_PORT`: host port exposed for the reverse proxy.
- `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`: PostgreSQL bootstrap
values.
- `DATABASE_HOST`, `DATABASE_PORT`, `DATABASE_NAME`, `DATABASE_USER`,
`DATABASE_PASSWORD`: database connection used by the API. With the provided
Compose file, `DATABASE_HOST=postgres`.
- `MIGRATE_ON_STARTUP`: when `true`, the API container applies SQL migrations
before starting the server.
Start the stack from `server/`:
```bash
docker compose up -d --build
```
The `api` service waits for the PostgreSQL healthcheck, runs `/app/bin/migrate`,
then launches `/app/bin/server`. Migrations are copied into the image under
`/app/migrations`.
To inspect the fully interpolated Compose configuration:
```bash
docker compose config
```
## Gitea Registry
Build and push the server image to a Gitea Container Registry with no registry
or credential value stored in the repository:
```bash
GITEA_REGISTRY=gitea.example.com \
GITEA_OWNER=my-org \
GITEA_IMAGE_NAME=gametime-server \
GITEA_IMAGE_TAG=latest \
GITEA_USERNAME=my-user \
GITEA_TOKEN='replace-with-token' \
./scripts/push-gitea-image.sh
```
`GITEA_IMAGE_TAG` defaults to `latest` when omitted. The script uses
`docker login --password-stdin` so the token is not printed by the command line.
## Scope
Ticket #47 only scaffolds the Dart server, the hexagonal directory layout and
the `/health` endpoint. Ticket #49 adds the first PostgreSQL schema. Ticket #48
adds authentication. Ticket #50 adds sync. Ticket #51 adds targeted sharing.
Ticket #52 adds Docker Compose packaging and Gitea registry publishing.
Upcoming tickets will fill the empty adapters and use cases:
- #52: Docker and registry packaging.
- #53: API, contract and integration tests.

View File

@ -0,0 +1,42 @@
services:
api:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
PORT: 8080
MIGRATE_ON_STARTUP: ${MIGRATE_ON_STARTUP:?set MIGRATE_ON_STARTUP}
DATABASE_HOST: ${DATABASE_HOST:?set DATABASE_HOST}
DATABASE_PORT: ${DATABASE_PORT:?set DATABASE_PORT}
DATABASE_NAME: ${DATABASE_NAME:?set DATABASE_NAME}
DATABASE_USER: ${DATABASE_USER:?set DATABASE_USER}
DATABASE_PASSWORD: ${DATABASE_PASSWORD:?set DATABASE_PASSWORD}
ports:
- "${API_BIND_ADDRESS:?set API_BIND_ADDRESS}:${API_PORT:?set API_PORT}:8080"
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB:?set POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER:?set POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test:
[
"CMD-SHELL",
"pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""
]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
volumes:
postgres_data:

View File

@ -2,4 +2,7 @@
Operational scripts for the server package.
Docker image publishing is planned for ticket #52.
- `docker-entrypoint.sh`: container entrypoint; applies migrations when
`MIGRATE_ON_STARTUP=true`, then executes the server command.
- `push-gitea-image.sh`: builds and pushes the Docker image to a Gitea
Container Registry using environment variables only.

View File

@ -0,0 +1,8 @@
#!/usr/bin/env sh
set -eu
if [ "${MIGRATE_ON_STARTUP:-true}" = "true" ]; then
/app/bin/migrate
fi
exec "$@"

View File

@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail
require_env() {
local name="$1"
if [[ -z "${!name:-}" ]]; then
echo "Missing required environment variable: ${name}" >&2
exit 1
fi
}
require_env GITEA_REGISTRY
require_env GITEA_OWNER
require_env GITEA_IMAGE_NAME
require_env GITEA_USERNAME
require_env GITEA_TOKEN
GITEA_IMAGE_TAG="${GITEA_IMAGE_TAG:-latest}"
IMAGE_REF="${GITEA_REGISTRY}/${GITEA_OWNER}/${GITEA_IMAGE_NAME}:${GITEA_IMAGE_TAG}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SERVER_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
docker build -t "${IMAGE_REF}" "${SERVER_DIR}"
printf '%s' "${GITEA_TOKEN}" | docker login "${GITEA_REGISTRY}" \
--username "${GITEA_USERNAME}" \
--password-stdin
docker push "${IMAGE_REF}"
echo "Pushed ${IMAGE_REF}"