Skip to content

chore: harden VPS deploy user with rootless Docker#156

Merged
GitAddRemote merged 19 commits into
mainfrom
feature/ISSUE-129
May 11, 2026
Merged

chore: harden VPS deploy user with rootless Docker#156
GitAddRemote merged 19 commits into
mainfrom
feature/ISSUE-129

Conversation

@GitAddRemote
Copy link
Copy Markdown
Owner

@GitAddRemote GitAddRemote commented May 7, 2026

Closes #129

Summary

  • Implements rootless Docker for the deploy user — the daemon runs entirely within the user's namespace with no root socket and no docker group membership. A compromised deploy key cannot escalate to root or affect other services.
  • bootstrap-vps.sh: adds uidmap + dbus-user-session prerequisites, enables linger, sets DOCKER_HOST/PATH in .bashrc, installs rootless Docker via runuser, enables and starts the user systemd service
  • Deploy scripts (deploy.sh, deploy-staging.sh, backup-db.sh, restore-db.sh, staging-up.sh, staging-down.sh): no changes to docker compose calls — rootless Docker requires no sudo
  • infra/docs/vps-setup.md: documents rootless Docker as the implemented approach, records pre-check results, verification steps, and a before/after security properties table

Pre-check results

Check Result
unprivileged_userns_clone 1
newuidmap absent, installed via uidmap apt package
unshare --user works ✓

Test plan

  • Run bootstrap-vps.sh on the VPS
  • systemctl --user status docker shows active as deploy user
  • docker run hello-world succeeds without sudo as deploy user
  • ls /var/run/docker.sock is denied for deploy user
  • groups does not include docker for deploy user
  • End-to-end deploy via release branch succeeds

Replace docker group membership (root-equivalent socket access) with a
sudoers file that permits only 'docker compose' and 'docker exec'.

- bootstrap-vps.sh: replace usermod -aG docker with /etc/sudoers.d/deploy-docker
- All deploy/backup/restore scripts: prefix docker compose calls with sudo
- infra/docs/vps-setup.md: document Option B (implemented) and Option A
  (rootless Docker upgrade path) with pre-check, install, and verification steps
Copilot AI review requested due to automatic review settings May 7, 2026 02:18
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to harden VPS deployments by removing deploy’s membership in the docker group and instead granting narrowly-scoped sudo permissions for Docker operations used by the deploy/backup/restore scripts, plus documenting the approach.

Changes:

  • Add a /etc/sudoers.d/deploy-docker rule in bootstrap-vps.sh and stop adding the deploy user to the docker group.
  • Prefix deploy/backup/restore/staging Docker invocations with sudo.
  • Add infra/docs/vps-setup.md documenting the narrowed-sudoers approach and a rootless Docker upgrade path.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
infra/scripts/bootstrap-vps.sh Replaces docker-group membership with a sudoers-based permission model for Docker.
infra/scripts/deploy.sh Uses sudo docker compose for production deploy actions.
infra/scripts/deploy-staging.sh Uses sudo docker compose for staging deploy actions.
infra/scripts/staging-up.sh Uses sudo docker compose for bringing staging up and checking status.
infra/scripts/staging-down.sh Uses sudo docker compose for bringing staging down.
infra/scripts/backup-db.sh Uses sudo docker compose exec for Postgres dumps.
infra/scripts/restore-db.sh Uses sudo docker compose to stop/start backend and restore DB via psql.
infra/docs/vps-setup.md Documents the VPS deploy-user hardening approach and rootless Docker alternative.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread infra/scripts/bootstrap-vps.sh Outdated
Comment thread infra/scripts/bootstrap-vps.sh Outdated
Comment thread infra/scripts/bootstrap-vps.sh Outdated
Comment thread infra/docs/vps-setup.md Outdated
Comment thread infra/docs/vps-setup.md Outdated
Comment thread infra/scripts/deploy.sh Outdated
Comment thread infra/scripts/deploy-staging.sh Outdated
Comment thread infra/scripts/staging-up.sh Outdated
Comment thread infra/scripts/staging-down.sh Outdated
Comment thread infra/scripts/bootstrap-vps.sh Outdated
VPS pre-check confirmed user namespace support (unprivileged_userns_clone=1,
unshare works). uidmap package was absent but is installable via apt, so
rootless Docker is viable.

- bootstrap-vps.sh: add uidmap + dbus-user-session to apt installs, replace
  sudoers block with rootless Docker setup (loginctl enable-linger, .bashrc
  DOCKER_HOST/PATH, rootless install via runuser, systemctl --user enable/start)
- Deploy scripts: revert sudo prefix — rootless Docker needs no sudo
- infra/docs/vps-setup.md: document Option A as implemented, record pre-check
  results, verification steps, and security properties table
Step-by-step guide for migrating an existing VPS (with live containers)
from the root Docker daemon to rootless, including postgres data
preservation via pg_dump/restore and docker group removal.
Copilot AI review requested due to automatic review settings May 7, 2026 05:25
- Remove deploy user from docker group on re-run (gpasswd -d) so a
  previous bootstrap that added docker group membership is cleaned up
- Use absolute paths in deploy.sh, deploy-staging.sh, staging-up.sh,
  and staging-down.sh instead of relying on cd; eliminates ambiguity
  and makes invocation context-independent
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Comment thread infra/scripts/bootstrap-vps.sh Outdated
Comment thread infra/scripts/bootstrap-vps.sh
Comment thread infra/scripts/bootstrap-vps.sh Outdated
Comment thread infra/docs/vps-setup.md Outdated
- Add DOCKER_HOST self-initialization to all deploy/backup/restore scripts
  so they work correctly in cron and non-interactive SSH sessions
- Fix root-ownership risk on ~deploy/.bashrc by chowning after append
- Clarify bootstrap-vps.sh comment: deploy user has no access to root
  Docker socket, which still exists at /var/run/docker.sock for system use
- Fix self-contradictory newuidmap table row in vps-setup.md
- Add rootless-docker-migration.md documenting the live station-bot
  migration including AppArmor profile requirement and post-mortem
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Comment thread infra/scripts/bootstrap-vps.sh Outdated
Comment thread infra/scripts/bootstrap-vps.sh Outdated
Comment thread infra/scripts/bootstrap-vps.sh Outdated
Comment thread infra/docs/vps-setup.md Outdated
Comment thread infra/docs/vps-setup.md
- Fix .bashrc comment: DOCKER_HOST only affects interactive/login shells,
  not cron — non-interactive scripts set it themselves
- Fix hardcoded UID in .bashrc heredoc: use quoted heredoc so $(id -u)
  evaluates dynamically at login rather than being baked in at bootstrap time
- Add AppArmor profile creation to bootstrap-vps.sh for Ubuntu 24.04+
  where unprivileged user namespaces are restricted by default
- Update vps-setup.md 'fully automated' claim to mention AppArmor handling
- Fix Phase 3 pg_dump command in migration runbook to use targeted grep
  extraction instead of set -a source, which fails on non-strict env files
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Comment thread infra/scripts/backup-db.sh
Comment thread infra/scripts/restore-db.sh
Comment thread infra/scripts/bootstrap-vps.sh Outdated
Comment thread infra/docs/vps-setup.md Outdated
Comment thread infra/docs/rootless-docker-migration.md Outdated
- backup-db.sh, restore-db.sh: replace set -a/source with grep-based
  variable extraction to avoid failures when env file contains values
  with spaces (e.g. APP_NAME=STATION BACKEND)
- bootstrap-vps.sh: tighten hardening comment — leaked key cannot
  escalate to root or access other users' containers, but can still
  affect deploy-user-owned resources
- vps-setup.md: qualify overview security claim to match actual
  rootless Docker guarantees
- rootless-docker-migration.md: fix "inaccessible to every other user"
  — root can still access the socket; correct to "non-root users"
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Comment thread infra/scripts/backup-db.sh Outdated
Comment thread infra/scripts/bootstrap-vps.sh Outdated
- backup-db.sh: add `|| true` to BACKUP_HEALTHCHECK_URL grep so a
  missing key doesn't abort the script under set -e
- bootstrap-vps.sh: derive AppArmor profile filename and rootlesskit
  binary path from DEPLOY_HOME instead of hard-coded /home/deploy,
  so the profile correctly targets the configured deploy user
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

Comment thread infra/scripts/backup-db.sh Outdated
Comment thread infra/scripts/restore-db.sh Outdated
Comment thread infra/scripts/restore-db.sh
Comment thread infra/docs/vps-setup.md Outdated
Comment thread infra/docs/vps-setup.md
Comment thread infra/scripts/bootstrap-vps.sh
- backup-db.sh, restore-db.sh: add || true to all required-var grep
  substitutions so missing keys reach the explicit :? error messages
  instead of silently aborting under set -euo pipefail
- restore-db.sh: add trap to remove temp file on EXIT so interrupted
  restores don't accumulate large artifacts in /tmp
- vps-setup.md Phase 3: use explicit DOCKER_HOST=unix:///var/run/docker.sock
  for the pg_dump step; rootless installer often switches CLI context
  immediately, making "don't source .bashrc" insufficient
- vps-setup.md Phase 5: use explicit DOCKER_HOST for rootless socket
  on the psql restore step to remove ambiguity
- bootstrap-vps.sh: skip rootless install if daemon is already healthy;
  clean up partial installs before retrying to avoid installer getting stuck
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

@GitAddRemote GitAddRemote requested a review from Copilot May 10, 2026 04:13
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Comment thread infra/docs/vps-setup.md Outdated
Comment thread infra/docs/rootless-docker-migration.md Outdated
Comment thread infra/docs/rootless-docker-migration.md Outdated
- vps-setup.md: remove PATH from the bootstrap-vps.sh description bullet;
  only DOCKER_HOST is written to .bashrc with the APT-based install
- rootless-docker-migration.md: fix cut -d= -f2 to cut -d= -f2- in the
  post-mortem Issue 1 example snippet to match the hardened parsing
  used throughout this PR
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comment thread infra/docs/rootless-docker-migration.md Outdated
- rootless-docker-migration.md: remove PATH from the "What changed"
  bullet; only DOCKER_HOST is written to .bashrc with the APT-based
  install, ~/bin is not needed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Comment thread infra/scripts/bootstrap-vps.sh Outdated
Comment thread infra/docs/vps-setup.md Outdated
- bootstrap-vps.sh: set XDG_RUNTIME_DIR explicitly for all
  systemctl --user invocations via runuser; without it systemctl
  cannot reach the user's D-Bus/systemd instance in non-interactive
  contexts (common error: failed to connect to bus)
@GitAddRemote GitAddRemote changed the title chore: harden VPS deploy user with narrowed sudoers chore: harden VPS deploy user with rootless Docker May 11, 2026
@GitAddRemote GitAddRemote requested a review from Copilot May 11, 2026 03:24
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comment thread infra/scripts/restore-db.sh Outdated
- restore-db.sh: set BACKEND_STOPPED=1 before docker compose stop
  rather than after; if stop exits non-zero under set -e the EXIT trap
  fires immediately and would have seen flag=0, skipping the restart —
  setting it first ensures the cleanup handler always attempts to bring
  the backend back up on any failure at or after the stop step
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Comment thread infra/docs/vps-setup.md Outdated
Comment thread infra/docs/vps-setup.md Outdated
- vps-setup.md: rewrite overview to accurately state that a leaked key
  gives SSH access and arbitrary container execution as the deploy user;
  the key hardening property is preventing root escalation via Docker,
  not restricting to deploy-related operations only
- vps-setup.md: replace misleading "Survive deploy key compromise" table
  row with "Blast radius of leaked key: Full host (root) → Deploy user only"
  which accurately describes what rootless Docker actually achieves
- rootless-docker-migration.md: tighten the Why section — a compromised
  key cannot escalate to root or access other users' containers via Docker,
  but blast radius is correctly scoped to the deploy user's namespace
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comment thread infra/docs/rootless-docker-migration.md Outdated
- rootless-docker-migration.md Issue 4: replace hard-coded curl|sh
  install paths (/home/deploy/bin/dockerd-rootless-setuptool.sh,
  rm -f /home/deploy/bin/dockerd) with APT-based equivalents;
  dockerd-rootless-setuptool.sh is in /usr/bin and dockerd is not
  placed in ~/bin with the APT install — cleanup is now the setup
  tool uninstall + rm -rf ~/.local/share/docker, with a note for
  anyone cleaning up an old curl|sh install
- rootless-docker-migration.md Prerequisites: add historical record
  callout to clarify the apt block reflects the original curl|sh
  migration and that the recommended method now includes
  docker-ce-rootless-extras
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

@GitAddRemote GitAddRemote merged commit 36a73ba into main May 11, 2026
5 checks passed
@GitAddRemote GitAddRemote deleted the feature/ISSUE-129 branch May 11, 2026 04:11
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.

chore: harden VPS deploy user — rootless Docker or narrowed sudoers

2 participants