diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 000000000..13b59fca5 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,78 @@ +name: Build & Publish Docker Image + +on: + push: + branches: [main] + tags: ["v*"] + pull_request: + branches: [main] + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +# Cancel in-progress runs when a new commit is pushed to the same ref. +concurrency: + group: docker-publish-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-push: + name: Build & push multi-arch image + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU (multi-arch) + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + # Skip login on PRs from forks — they don't have package:write. + # Image is built but not pushed in that case. + - name: Log in to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute image tags & labels + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable={{is_default_branch}} + type=sha,prefix=sha-,format=short + labels: | + org.opencontainers.image.title=HealthLog + org.opencontainers.image.description=Self-hosted health-tracking PWA + org.opencontainers.image.licenses=AGPL-3.0-only + org.opencontainers.image.source=https://github.com/${{ github.repository }} + + - name: Build & push image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + provenance: true + sbom: true diff --git a/README.md b/README.md index 250c930a9..b02ff8ad8 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Most health apps lock your data behind proprietary clouds, push subscriptions, a ## Quick Start -Plan ~5 minutes for a working install. The bundled `docker-compose.yml` builds the app from source — pre-built images on GHCR are coming in a separate release. +Plan ~5 minutes for a working install. The bundled `docker-compose.yml` pulls a pre-built multi-arch image (`linux/amd64` + `linux/arm64`) from [GitHub Container Registry](https://github.com/MBombeck/HealthLog/pkgs/container/healthlog); no build step required for self-hosters. Contributors who want to test local changes can `docker compose up --build`. ```bash git clone https://github.com/MBombeck/HealthLog.git diff --git a/SECURITY.md b/SECURITY.md index 8e24b8d2c..fadaf9b98 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -5,7 +5,7 @@ Only the latest release on the `main` branch is actively supported with security updates. | Version | Supported | -|---------|-----------| +| ------- | --------- | | Latest | Yes | | Older | No | @@ -59,3 +59,17 @@ HealthLog is designed with security as a core principle: - **Proxy-level route protection** via `proxy.ts` (session cookie validation on all non-public paths) For more details, see the [security documentation](https://docs.healthlog.dev/security/overview/). + +## Supply Chain + +HealthLog Docker images are built and published from this repository's CI: + +- **Source**: [`.github/workflows/docker-publish.yml`](.github/workflows/docker-publish.yml) — runs on every push to `main` and every `v*` tag +- **Registry**: [`ghcr.io/mbombeck/healthlog`](https://github.com/MBombeck/HealthLog/pkgs/container/healthlog) +- **Architectures**: `linux/amd64` and `linux/arm64` +- **Provenance attestation**: each image carries a [SLSA build provenance](https://slsa.dev/spec/v1.0/provenance) statement linking it back to the GitHub Actions run, the commit SHA, and the workflow definition +- **SBOM**: each image includes a [Software Bill of Materials](https://docs.docker.com/build/metadata/attestations/sbom/) you can inspect with `docker buildx imagetools inspect ghcr.io/mbombeck/healthlog:latest --format '{{ json .SBOM }}'` + +To pin to a specific version in production, replace `:latest` with the released tag in your `docker-compose.yml`, e.g. `ghcr.io/mbombeck/healthlog:1.2.0`. Pinning is recommended for self-hosters who want explicit control over upgrades. + +If you discover a tampered or unexpected image, please report it as a vulnerability via the email above. diff --git a/docker-compose.yml b/docker-compose.yml index c990c15bc..f5405eae6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,12 @@ services: app: + # Pre-built multi-arch images are published to GHCR by + # .github/workflows/docker-publish.yml on every push to main and on + # tagged releases. Pin to a specific version in production: + # e.g. ghcr.io/mbombeck/healthlog:1.2.0 + image: ghcr.io/mbombeck/healthlog:latest + # Fallback build context: if the image is not yet in the registry, + # or you want to test local changes, run `docker compose up --build`. build: context: . dockerfile: Dockerfile