LW IT Solutions logo LW IT Solutions logo LW IT Solutions
« Blog Overview /Raspberry PI / Automatically Testing and Updating Docker Applications on...
Read this article in other languages:

Automatically Testing and Updating Docker Applications on a Raspberry Pi

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

Automatically Testing and Updating Docker Applications on a Raspberry PiAutomatically Testing and Updating Docker Applications on a Raspberry Pi

Management Summary

CI/CD is a process that automatically checks software changes and delivers a new version. On a Raspberry Pi running local services, it can replace repeated manual update steps and make releases easier to trace. The article describes a controlled path from an update request to a running application. Clear responsibility and limited permissions help prevent conflicting changes. Automation still needs backups and a recovery plan, because installing an older program does not automatically reverse changes to stored data.

Contents
  1. 1. A reproducible release
  2. 2. Webhook validation before deployment
  3. 3. Service health and recovery
  4. 4. Network access and 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. 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. An image digest is a content-based fingerprint that identifies a particular image version. 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
The request is authenticated, the target authorized, a fixed release deployed and the application checked.

2. Webhook validation 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, whose duration does not reveal how much of the expected signature matches. 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. Service health and 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. Network access and 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 runner is the program that executes workflow jobs. 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. Starting a Raspberry Pi 4 or 5 from an SSD: Setup and Recovery
  2. Keeping a Raspberry Pi Powered During an Outage: Five UPS Options Compared
  3. Automatically Testing and Updating Docker Applications on a Raspberry Pi
  4. Docker Compose Home Server: Traefik, TLS and Update Monitoring
  5. Jellyfin on Raspberry Pi 5: Playing Videos While Avoiding Costly Conversion
  6. Accessing a Home Network with WireGuard: Setting Up a Raspberry Pi VPN Gateway
  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 and formconfirmed 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.

LUKASWOJCIK.COM · NEWSLETTER

Fresh ideas in your inbox

New articles, practical tools and updates from the projects.

Choose topics (optional)
No selection: all topics.

Subscription starts after confirmation by email. · Privacy

Privacy · Newsletter

Email address, language, selected topics and subscription/confirmation dates are stored to deliver the newsletter. Double opt-in is required. Every issue includes an immediate unsubscribe link. Formconfirmed checks the signup for spam. No email open or click tracking.

Read the full privacy policy ↗