diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..2d1f4746 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,34 @@ +# Keep the build context small and avoid baking secrets/dev artifacts into +# image layers. The Dockerfile does `COPY . /src`, so anything not ignored +# here ends up in the build stage. + +# Version control +.git +.jj + +# Local environment and secrets +.env +*.env +!dev-template.env + +# Python virtualenv and caches +.venv +.*_cache +__pycache__/ +*.egg-info/ + +# Tooling and test artifacts +.tox +.coverage* +coverage.xml + +# Generated/local runtime data +run/ + +# Docs build output +docs/_build/ + +# Docker files themselves don't belong in the image +Dockerfile +.dockerignore +docker-compose*.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7add55c5..6751e155 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,13 +40,11 @@ jobs: - name: "zizmor" python: "3.14" tox: zizmor - name: ${{ matrix.name }} runs-on: ubuntu-24.04 permissions: contents: read id-token: write - steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: @@ -64,3 +62,39 @@ jobs: if: ${{ matrix.coverage }} with: use_oidc: true + + publish: + name: "Publish image" + needs: main + if: ${{ github.event_name == 'push' }} + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 + id: meta + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=raw,value=latest + type=sha,format=long + - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + GIT_SHA=${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..cdd777bc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,121 @@ +FROM python:3.14.6-slim-trixie AS base + +# -------------------------------------------- + +FROM base AS build +SHELL ["sh", "-exc"] + +# Install uv (pinned for reproducible builds) +COPY --from=ghcr.io/astral-sh/uv:0.11.21 /uv /usr/local/bin/uv + +# - Compile Python bytecode for faster app startup. +# - Silence uv complaining about not being able to use hard links. +# - Set the project virtualenv to /app. +# - Pick a Python. +# - Prevent uv from accidentally downloading isolated Python builds. +ENV UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy \ + UV_PROJECT_ENVIRONMENT=/app \ + UV_PYTHON=/usr/local/bin/python \ + UV_PYTHON_DOWNLOADS=never + +# Install app dependencies +COPY pyproject.toml /_lock/ +COPY uv.lock /_lock/ +RUN --mount=type=cache,target=/root/.cache <