# syntax=docker/dockerfile:1 # # Single-container image for Velodrome (docs/DECISIONS.md D15/D16): Caddy serves the SvelteKit # static SPA and reverse-proxies /api/* to the FastAPI app, both running in the same container # behind whatever TLS-terminating reverse proxy already exists on the host. Build context is the # repo root, since this needs both apps/api and apps/web: # # docker build -t velodrome . # # Replaces apps/api/Dockerfile and apps/web/Dockerfile from the old 4-container compose plan # (PR #4, closed as superseded) — those built two images meant to run as separate services; # this builds one. # ---- web: produces /app/build, nothing from this stage ends up running ---- FROM node:22-slim AS web-builder WORKDIR /app COPY apps/web/package.json apps/web/pnpm-lock.yaml ./ RUN corepack enable && corepack prepare pnpm@9 --activate \ && pnpm install --frozen-lockfile COPY apps/web . RUN pnpm run build # ---- api: produces the venv at /app/.venv ---- FROM python:3.12-slim AS api-builder RUN pip install --no-cache-dir uv WORKDIR /app COPY apps/api/pyproject.toml apps/api/uv.lock ./ # Dependencies first, isolated from source changes, so touching velodrome/ doesn't invalidate # this layer. RUN uv sync --frozen --no-install-project --no-dev COPY apps/api/velodrome ./velodrome COPY apps/api/alembic ./alembic COPY apps/api/alembic.ini ./ RUN uv sync --frozen --no-dev # ---- runtime ---- FROM python:3.12-slim AS runtime # Caddy's official images ship a single statically-linked Go binary (no CGO) — copying it out of # the upstream image is the standard way to get Caddy into a non-Caddy base image without a # second package manager or a source build. COPY --from=caddy:2 /usr/bin/caddy /usr/bin/caddy # tini is PID 1: reaps zombies and forwards signals correctly to entrypoint.sh's two background # processes, which a bare `CMD` running a shell script as PID 1 would not do on its own. RUN apt-get update && apt-get install -y --no-install-recommends tini \ && rm -rf /var/lib/apt/lists/* RUN groupadd --system velodrome && useradd --system --gid velodrome --create-home velodrome WORKDIR /app COPY --from=api-builder --chown=velodrome:velodrome /app /app COPY --from=web-builder --chown=velodrome:velodrome /app/build /srv/web COPY --chown=velodrome:velodrome deploy/Caddyfile /etc/caddy/Caddyfile COPY --chown=velodrome:velodrome deploy/entrypoint.sh /app/entrypoint.sh RUN chmod +x /app/entrypoint.sh ENV PATH="/app/.venv/bin:$PATH" # Absolute path into the mounted volume below. See deploy/README.md for the full env var table — # this is the one variable NOT meant to be overridden per-deployment, since /data is the contract # with the volume mount, not a per-instance setting. ENV VELODROME_DATABASE_URL="sqlite+aiosqlite:////data/velodrome.db" RUN mkdir -p /data && chown velodrome:velodrome /data USER velodrome VOLUME ["/data"] # Non-privileged port: Caddy needs no root/setcap here, and TLS termination is the host reverse # proxy's job (docs/PLAN.md "Service topology"), not this container's. EXPOSE 8080 ENTRYPOINT ["tini", "--"] CMD ["/app/entrypoint.sh"]