Episode 2: Forgejo Actions — runner, Docker sandbox, own registry, locked-down deploy

This commit is contained in:
Maximilian Grimm 2026-08-19 22:21:20 +02:00
parent 06f8e8812e
commit 0341ae777d
8 changed files with 231 additions and 1 deletions

View file

@ -92,3 +92,35 @@ Plus: enable your provider's server snapshots. Two clicks, saves your company.
Not covered here, on purpose (own episodes): SSO everywhere, 2FA enforcement
per service, Netbird zero-trust mesh, central logging, CrowdSec, offsite borg.
Baseline first. Perfect later.
## CI: the two ways to hand your server away
Both of these look like shortcuts and both give anyone who can push a branch full
control of the machine.
**1. Mounting the host Docker socket into the runner.** Every forum answer suggests
it, because it works instantly:
```yaml
volumes:
- /var/run/docker.sock:/var/run/docker.sock # do not do this
```
A job can then start a privileged container on the host, mount `/` and read every
volume on the box, including the password manager sitting in the next container.
Use the docker-in-docker sidecar in `compose.yml` instead. It costs six lines and
the jobs talk to a daemon that has nothing of yours in it.
**2. An unrestricted SSH key for the deploy step.** If the key in your CI secrets
can run any command, the pipeline is a root shell with extra steps. Lock it to one
command in `authorized_keys`:
```
command="/usr/local/bin/deploy-myapp",no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-ed25519 AAAA...
```
The script receives the argument from the job in `$SSH_ORIGINAL_COMMAND`. Sanitise
it — the example in `examples/deploy-hello-ops.sh` strips everything that is not a
hex character, because it only ever expects an image tag. The deploy user needs
Docker access, so the forced command is the thing containing it; without the forced
command, membership in the `docker` group is equivalent to root.