Skip to content

Fix and harden the deploy pipeline after the app-user move#2

Merged
richardhapb merged 2 commits into
mainfrom
fix/deploy-after-user-move
Jul 21, 2026
Merged

Fix and harden the deploy pipeline after the app-user move#2
richardhapb merged 2 commits into
mainfrom
fix/deploy-after-user-move

Conversation

@richardhapb

Copy link
Copy Markdown
Owner

What

The deploy job broke when the app moved from the SSH user's home to a dedicated nologin app user's directory. Instead of patching paths, this hardens the whole deploy boundary:

  • Workflow: the deploy step is now a single sudo /usr/local/sbin/finitum-deploy "$IMAGE_TAG". No paths, usernames, or registry logins appear in the (public) workflow file. The broken GHCR login (nonexistent GHCR_PULL_TOKEN secret; package is public anyway) is gone.
  • Server (already provisioned and smoke-tested): a root-owned finitum-deploy script holds all environment detail -- it validates the image tag against a whitelist (blocks argument injection across the sudo boundary), does git pull --ff-only as the app user, runs compose as root (the app user's .env files are 0600 and it has no docker access), and prunes only this repo's images. CI authenticates as a new low-privilege deploy user whose sudoers permits exactly that one script, with a forwarding-restricted SSH key. A leaked deploy key can deploy and nothing else.
  • Immutable deploys: docker-compose.prod.yml now consumes ${IMAGE_TAG:-latest}, so the queued-deploy concurrency actually ships the sha that triggered it; the latest fallback keeps manual docker compose runs on the box unchanged.
  • Secrets rotated: HOST_USER/HOST_SSH_KEY now point at the deploy user with a fresh ed25519 key; the unused DEPLOY_PATH variable was deleted. The server repo's origin was switched to HTTPS (anonymous read-only; no GitHub credentials on the box).
  • docs/deployment.md documents the pipeline and security model.

Verification

  • SSH as deploy with the new key: works; sudo whoami correctly denied; injection attempt ("latest; rm -rf /") correctly rejected by tag validation.
  • sudo finitum-deploy latest --check (read-only: git fetch as app user + compose config): passes on the VPS.
  • visudo -cf validates the sudoers entry; workflow YAML parses.
  • First real end-to-end run happens on merge to main.

🤖 Generated with Claude Code

- Deploy step runs a single root-owned finitum-deploy script on the
  server via a deploy user restricted by sudoers to exactly that
  command; no paths, users, or registry logins in the workflow.
- docker-compose.prod.yml consumes the immutable sha tag via
  ${IMAGE_TAG:-latest} instead of always deploying mutable latest.
- Document the security model in docs/deployment.md.
@richardhapb
richardhapb merged commit f1b9c97 into main Jul 21, 2026
4 checks passed
@richardhapb
richardhapb deleted the fix/deploy-after-user-move branch July 21, 2026 06:38
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