Skip to content

ci(deploy): build once on GitHub, secret-scan, push to GHCR; server pulls by SHA - #742

Merged
mrviduus merged 2 commits into
mainfrom
ci/build-once-deploy-many
Oct 6, 2026
Merged

mrviduus merged 2 commits into
mainfrom
ci/build-once-deploy-many

Conversation

@mrviduus

@mrviduus mrviduus commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Owner-approved "build once, deploy many".

Secrets (the gate): audit with a canary .env + planted files (dirty-checkout simulation), scanning env, history and every layer of all 6 images. A clean CI checkout leaked nothing; a dirty checkout leaked untracked apps/admin/.env*, packages/shared/.env.local, appsettings.Production.json and a PEM into 5 images — root .dockerignore excluded only the root .env. Fixed (.env* at any depth except .env.example, appsettings.*.json, keys, service-account json, bin/obj); pnpm store removed from admin/ssg images. scripts/scan-image-secrets.sh gates the push (positive control verified). Only build arg anywhere: GIT_SHA.

Flow:

  • images.yml (replaces ci-cache-warm.yml): build with gha cache → scan → push ghcr.io/mrviduus/textstack-<svc>:<full sha> with GITHUB_TOKEN.
  • deploy.yml: images job alongside ci; deploy checks out this run's SHA (ff-only), logs in with GITHUB_TOKEN into a temp DOCKER_CONFIG, pulls each image (900 s), up -d --no-build; any failed pull falls back to up -d --build. Retags :latest; removes old GHCR tags. Rollback pulls by SHA.
  • docker-compose.yml: image: ${IMAGE_REGISTRY:-textstack}-<svc>:${IMAGE_TAG:-latest} — unset = old names; local dev unchanged. Merged with fix(privacy): policy, delete page and Play Data Safety match the code; no query strings in logs; container log rotation #738's logging anchors.

Cost: $0 — public repo Actions minutes free; GHCR free for public packages; GITHUB_TOKEN pulls free. First push creates private packages; owner flips to public after a clean scan.

Code review: one finding (deploy pulled main's tip, not its own SHA) fixed. actionlint clean; docker compose config OK.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TBqXDqcbLYbPwGKnTsWYLz

mrviduus and others added 2 commits October 6, 2026 13:16
…y SHA

- images.yml (replaces ci-cache-warm.yml): build compose images, secret-scan
  every layer (canary .env, keys, token shapes) before push, tag :<full sha>
- deploy.yml: images job next to ci; server pulls that SHA, falls back to
  --build; rollback pulls/publishes the rollback SHA; old tags cleaned up
- .dockerignore: nested .env*, appsettings.*.json, keys, service accounts,
  bin/obj (audit found all of these reach images from a dirty checkout)
- admin/ssg-worker: drop pnpm store from the install layer
- docker-compose.yml: image: ${IMAGE_REGISTRY:-textstack}-<svc>:${IMAGE_TAG:-latest}

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TBqXDqcbLYbPwGKnTsWYLz
…line

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TBqXDqcbLYbPwGKnTsWYLz
@mrviduus
mrviduus enabled auto-merge (squash) October 6, 2026 17:17
@mrviduus
mrviduus merged commit 1d0be3e into main Oct 6, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant