Domain 2 capstone -- run a service the way it should be run

capstone · 90 min · Objective 2.6

Task

Bring the Services and User Management domain together: a containerised application with persistent data, its own network, least privilege, a dedicated service account on the host, a systemd unit that manages it, and a scheduled backup. Prove each property rather than assuming it.

Steps

  1. Least-privilege image. Build the app image with a non-root USER, a pinned base tag, and dependencies installed before source is copied. Prove the container runs as a non-root user.
  2. Data and network. Run the app and its database on a user-defined network with a named volume for the data, the database reachable by name, and the published port bound to loopback behind a reverse proxy.
  3. Host account. Create a dedicated service account with nologin and no password to own the deployment, and confirm it cannot be logged into.
  4. systemd management. Generate or write a systemd unit that runs the container, enable --now it, and prove it starts on boot -- the enable-versus-start distinction -- and restarts on failure.
  5. Rootless. Run the whole thing rootless with Podman and explain what a container escape yields under rootless versus under a root daemon.
  6. Backup. Add a systemd timer that dumps the database with the application's own tool (not a tar of the live data directory) to a location the container cannot write to, and prove a restore works.
  7. Prove persistence. Restart the host, and confirm the service comes back and the data is intact.
  8. Write an evidence summary tying each property to the command that proves it.

Verify

podman inspect app --format '{{.Config.User}}'            # non-root
podman exec app getent hosts db                            # resolves by name
getent passwd svc-app | cut -d: -f7                        # nologin
systemctl is-enabled container-app                         # enabled
ss -tlnp | grep ':8080'                                    # 127.0.0.1 only
# after a host reboot:
systemctl is-active container-app && \
  podman exec db psql -tAc 'select count(*) from items'    # running, data intact

The capstone passes when the service survives a reboot with its data, runs as a non-root user rootless, is reachable only where intended, and has a restore you have actually performed. A backup you have not restored does not count, and a unit you started but did not enable will not survive the reboot that tests it.

Notes

The domain's recurring lessons all appear here: data outside a volume is lost; enable is not start; a service account should not be able to log in; rootless turns an escape into an unprivileged user; and a backup is a hypothesis until restored. The capstone is where they stop being separate facts and become one running system.