LW IT Solutions logo LW IT Solutions logo LW IT Solutions
« Blog Overview /Raspberry PI / Local CI/CD for Raspberry Pi: Automating Docker...
Read this article in other languages:

Local CI/CD for Raspberry Pi: Automating Docker Compose Deployments

Part 3 of 7 in the series Homelab on the Raspberry Pi

Local CI/CD for Raspberry Pi: Automating Docker Compose Deployments
Contents
  1. 1. Define a reproducible release
  2. 2. Validate the webhook before deployment
  3. 3. Check service health and plan recovery
  4. 4. Separate network access from deployment authority
  5. 5. GitHub Actions and restricted SSH keys
  6. 6. Scheduled image updates
  7. Questions and answers
  8. Sources

A local CI/CD pipeline can test changes and deploy Docker Compose services on a Raspberry Pi without a separate SSH session for every update. Git stores the deployment definition; a local worker applies an approved version. Backups, monitoring and recovery procedures remain necessary.

1. Define a reproducible release

CI means automated integration checks; CD covers delivery or deployment. A webhook starts a job, but does not provide those checks by itself. In a GitOps approach, Git records the desired configuration and an automated process reconciles the running services with that configuration.

  • Configuration: Compose describes services, networks and volume mounts. Volume contents and database records are separate data, not part of the YAML file. Secrets belong outside an ordinary Git repository.
  • Release identity: The exact Git commit and image digests identify the release. A moving tag such as :latest does not identify a reproducible release.
  • Recovery: Git history helps trace changes, but is not inherently immutable. Restoring an older configuration does not reverse database migrations or restore deleted files. Backups and a tested recovery path are separate requirements.
Validated deployment on the Raspberry Pi
Authenticate the request, authorize the target, deploy a fixed release and check the application.

2. Validate the webhook before deployment

A listener such as adnanh/webhook receives the repository event. For GitHub, validation of X-Hub-Signature-256 uses the unchanged request body, the webhook secret and a constant-time comparison. HMAC verifies the signed payload; deployment permission still requires explicit checks of the event, repository and allowed branch.

  1. Invalid or disallowed requests are rejected. Recorded delivery IDs help detect duplicate deliveries and prevent repeated execution.
  2. A validated job enters a queue without Docker access. Arbitrary payload fields must not become shell commands.
  3. A separate worker fetches the approved commit into a controlled release directory. Serialized deployments prevent concurrent changes to the same stack.
  4. The worker validates the complete Compose configuration, prepares images through an explicit pull or build step, and then starts the release.

docker compose up -d --remove-orphans alone is not a complete deployment pipeline. Existing services with changed images or configuration are recreated; a newer image behind an unchanged remote tag is not guaranteed to be fetched by that command. The pull policy or a separate pull step must be deliberate. The Compose project name and the set of configuration files must remain consistent.

3. Check service health and plan recovery

docker compose up --wait --wait-timeout 120 waits for services to be running or healthy and implies detached mode. The 120-second limit is an example, not a universal startup allowance. A service without a health check is only checked for running state.

Docker HEALTHCHECK reports health after the configured intervals and retries. An unhealthy status alone does not trigger a restart or rollback in ordinary Docker Compose. The deployment worker needs an explicit failure policy, application-level checks and useful logs. A green process status does not establish that login, storage or database access works.

Local ARM builds can avoid cross-compilation, provided base images and dependencies support the target architecture. Builds also consume CPU, memory and storage on the Pi.

4. Separate network access from deployment authority

The network-facing listener should run without root privileges and without Docker socket access. Membership in the docker group grants root-level authority over a conventional rootful Docker daemon. A read-only socket proxy supports inspection, but cannot perform a deployment that changes containers.

A small privileged worker can accept only validated jobs for fixed repositories and deployment targets. Repository-controlled Compose files can still request powerful mounts or privileges, so the trusted code and configuration boundary matters. A management VLAN with firewall rules reduces reachable paths; isolation does not guarantee protection from compromised devices in an allowed network.

5. GitHub Actions and restricted SSH keys

A GitHub-hosted runner can reach the Pi over SSH through an authenticated private network or VPN; a publicly exposed SSH port is not inherently required. A self-hosted runner instead requests jobs from GitHub. The current reference lists Linux ARM64, but no longer lists ARM32. OS, architecture and individual action compatibility need checking before installation.

The runner needs outbound HTTPS on port 443 to GitHub; jobs may additionally need Git, registry and application connections. No inbound port is required for job assignment. A private repository does not make arbitrary workflow code trustworthy. Untrusted pull-request code must not run with deployment credentials or production Docker access.

A dedicated deployment key can use the following authorized_keys pattern. AAAA… is a placeholder for a complete public key:

restrict,command="/usr/local/bin/deploy-stack" ssh-ed25519 AAAA… ci-deploy

command= forces the configured command; restrict disables forwarding, PTY allocation and ~/.ssh/rc. The script must reject untrusted arguments, including SSH_ORIGINAL_COMMAND, and protect its dependencies and configuration from modification by the caller. A stolen key can still repeatedly trigger the permitted operation; a forced command is not a complete security boundary.

6. Scheduled image updates

The original containrrr/watchtower repository was archived on 17 December 2025. Community forks such as nickfedor/watchtower need a separate version and Docker API compatibility check before migration. Image polling recreates containers from newer images; it does not test application compatibility.

The linked Watchtower v1.14.3 reference documents a default polling interval of 86,400 seconds and a six-field WATCHTOWER_SCHEDULE. 0 0 4 * * * schedules 04:00; an explicit TZ=Europe/Warsaw makes the intended local time clear. The documented default timezone is UTC. Schedule and WATCHTOWER_POLL_INTERVAL are alternatives.

WATCHTOWER_LABEL_ENABLE=true selects containers carrying com.centurylinklabs.watchtower.enable=true. WATCHTOWER_CLEANUP=true removes old images after updates, but does not manage all logs or volume data and may remove a locally available rollback image. Each service needs one update mechanism: independently changing the same container through GitOps and Watchtower can produce conflicting states.

Questions and answers

Can a read-only Docker proxy deploy a Compose stack?

No. Creating, stopping or replacing containers needs write operations. The useful separation is a listener without Docker access and a controlled worker with the required authority. The worker still needs input validation, trusted configuration, deployment locking and audit logs.

Homelab on the Raspberry Pi

  1. Raspberry Pi 4 and 5: Booting From an SSD by Changing the Bootloader
  2. Raspberry Pi UPS: five options and selection criteria
  3. Local CI/CD for Raspberry Pi: Automating Docker Compose Deployments
  4. Docker Compose Home Server: Traefik, TLS and Update Monitoring
  5. Jellyfin on Raspberry Pi 5: Direct Play and Transcoding Limits
  6. Raspberry Pi as a Hardened WireGuard VPN Gateway with Split-Tunneling
  7. Raspberry Pi Monitoring with Prometheus and Grafana
Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Get in touch

A question or an idea for a web project? Tell me a little about it. This form uses reCAPTCHA to protect against spam.

Write a comment

Experience with other models and questions about the build are welcome here.

The email address is not published. Required fields are marked with an asterisk.