Make the container actually deployable, publish to GHCR

The Docker setup built but never came up. Three separate blockers, each
found by running the full compose stack against a real Postgres:

- The runner stage hand-copied prisma/@prisma/dotenv, which misses the
  CLI's transitive deps (valibot) and crash-looped the entrypoint. A
  dedicated migrator stage now installs the CLI at the exact lockfile
  version with its real dependency tree.
- Docker sets HOSTNAME to the container id and Next's standalone server
  binds to it verbatim, so the app was unreachable on localhost inside
  the container and the healthcheck could never pass. Pinned to 0.0.0.0.
- openssl was missing from the build stages but present at runtime, so
  Prisma detected a different platform and tried to download a matching
  query engine at boot, as an unprivileged user, into a root-owned
  directory. Aligned openssl across every stage that touches Prisma.

Verified from a clean volume: migrations apply, both containers report
healthy, /api/health returns 200, and the app serves from the image.

Also:
- Publish multi-arch images to GHCR on push to main and on v* tags, with
  an SBOM and a signed provenance attestation.
- Default compose to the published image; add a build override for
  working on the app itself. Host port and image are configurable.
- Drop two unused dependencies: canvas (19MB of native node-canvas; the
  pipeline uses @napi-rs/canvas) and multer (routes use formData()).
- Add a root .gitignore. There wasn't one, so a real .env was one
  `git add .` away from being committed. Untrack .DS_Store.
- Rewrite the README around what the tool does and how to run it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Aryan SinghandClaude Opus 5 committed 2026-09-01 23:54:57 -05:00
1 parent 4130ce2e09
commit 7e7e4d9b1c
36 files changed
+2260 -1270

No files matched your search

+84
View File
@@ -0,0 +1,84 @@
name: Publish Docker image
on:
push:
branches: [main]
tags: ["v*.*.*"]
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# Needed to attach a build provenance attestation to the pushed image.
id-token: write
attestations: write
steps:
- uses: actions/checkout@v4
# QEMU lets the amd64 runner emulate arm64 so one job produces both
# architectures (Apple Silicon + typical cloud hosts). The emulated arm64
# leg is the slow part of this workflow; if it starts to hurt, split into
# a matrix over ubuntu-latest + ubuntu-24.04-arm and merge the digests.
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# ghcr.io requires a lowercase image path; github.repository keeps the
# owner's original casing (geneticglitch1/XRayPDF), so normalize it.
- name: Compute lowercase image name
id: image
run: echo "name=${IMAGE_NAME,,}" >> "$GITHUB_OUTPUT"
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ steps.image.outputs.name }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,format=long
labels: |
org.opencontainers.image.title=XRayPDF
org.opencontainers.image.description=Strip hidden text and invisible layers out of PDFs
org.opencontainers.image.licenses=Apache-2.0
- name: Build and push
id: build
uses: docker/build-push-action@v6
with:
context: ./client
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: mode=max
sbom: true
- name: Attest build provenance
uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ env.REGISTRY }}/${{ steps.image.outputs.name }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true