{"id":429,"date":"2026-08-31T07:45:00","date_gmt":"2026-08-31T05:45:00","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/?p=429"},"modified":"2026-10-24T01:39:04","modified_gmt":"2026-10-23T23:39:04","slug":"local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments","status":"publish","type":"post","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/","title":{"rendered":"Local CI\/CD for Raspberry Pi: Automating Docker Compose Deployments"},"content":{"rendered":"<p>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.<\/p>\n<h2>1. Define a reproducible release<\/h2>\n<p>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.<\/p>\n<ul>\n<li><strong>Configuration:<\/strong> 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.<\/li>\n<li><strong>Release identity:<\/strong> The exact Git commit and image digests identify the release. A moving tag such as <code>:latest<\/code> does not identify a reproducible release.<\/li>\n<li><strong>Recovery:<\/strong> 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.<\/li>\n<\/ul>\n<figure class=\"lw-diagram\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/diagrams\/dia-429-cicd-20261024.png\" width=\"1120\" height=\"580\" alt=\"Validated deployment on the Raspberry Pi\"><figcaption>Authenticate the request, authorize the target, deploy a fixed release and check the application.<\/figcaption><\/figure>\n<h2>2. Validate the webhook before deployment<\/h2>\n<p>A listener such as <code>adnanh\/webhook<\/code> receives the repository event. For GitHub, validation of <code>X-Hub-Signature-256<\/code> 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.<\/p>\n<ol>\n<li>Invalid or disallowed requests are rejected. Recorded delivery IDs help detect duplicate deliveries and prevent repeated execution.<\/li>\n<li>A validated job enters a queue without Docker access. Arbitrary payload fields must not become shell commands.<\/li>\n<li>A separate worker fetches the approved commit into a controlled release directory. Serialized deployments prevent concurrent changes to the same stack.<\/li>\n<li>The worker validates the complete Compose configuration, prepares images through an explicit pull or build step, and then starts the release.<\/li>\n<\/ol>\n<p><code>docker compose up -d --remove-orphans<\/code> 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.<\/p>\n<h2>3. Check service health and plan recovery<\/h2>\n<p><code>docker compose up --wait --wait-timeout 120<\/code> 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.<\/p>\n<p>Docker <code>HEALTHCHECK<\/code> reports health after the configured intervals and retries. An <code>unhealthy<\/code> 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.<\/p>\n<p>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.<\/p>\n<h2>4. Separate network access from deployment authority<\/h2>\n<p>The network-facing listener should run without root privileges and without Docker socket access. Membership in the <code>docker<\/code> 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.<\/p>\n<p>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.<\/p>\n<h2>5. GitHub Actions and restricted SSH keys<\/h2>\n<p>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.<\/p>\n<p>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.<\/p>\n<p>A dedicated deployment key can use the following <code>authorized_keys<\/code> pattern. <code>AAAA\u2026<\/code> is a placeholder for a complete public key:<\/p>\n<pre><code>restrict,command=\"\/usr\/local\/bin\/deploy-stack\" ssh-ed25519 AAAA\u2026 ci-deploy<\/code><\/pre>\n<p><code>command=<\/code> forces the configured command; <code>restrict<\/code> disables forwarding, PTY allocation and <code>~\/.ssh\/rc<\/code>. The script must reject untrusted arguments, including <code>SSH_ORIGINAL_COMMAND<\/code>, 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.<\/p>\n<h2>6. Scheduled image updates<\/h2>\n<p>The original <code>containrrr\/watchtower<\/code> repository was archived on 17 December 2025. Community forks such as <code>nickfedor\/watchtower<\/code> need a separate version and Docker API compatibility check before migration. Image polling recreates containers from newer images; it does not test application compatibility.<\/p>\n<p>The linked Watchtower v1.14.3 reference documents a default polling interval of 86,400 seconds and a six-field <code>WATCHTOWER_SCHEDULE<\/code>. <code>0 0 4 * * *<\/code> schedules 04:00; an explicit <code>TZ=Europe\/Warsaw<\/code> makes the intended local time clear. The documented default timezone is UTC. Schedule and <code>WATCHTOWER_POLL_INTERVAL<\/code> are alternatives.<\/p>\n<p><code>WATCHTOWER_LABEL_ENABLE=true<\/code> selects containers carrying <code>com.centurylinklabs.watchtower.enable=true<\/code>. <code>WATCHTOWER_CLEANUP=true<\/code> 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.<\/p>\n<div class=\"lw-faq\">\n<h2>Questions and answers<\/h2>\n<h3>Can a read-only Docker proxy deploy a Compose stack?<\/h3>\n<p>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.<\/p>\n<\/div>\n<div class=\"lw-quellen\">\n<h2>Sources<\/h2>\n<ul>\n<li><a href=\"https:\/\/docs.docker.com\/compose\/\" target=\"_blank\" rel=\"noopener noreferrer\">Docker Compose documentation<\/a><\/li>\n<li><a href=\"https:\/\/docs.github.com\/en\/actions\/reference\/runners\/self-hosted-runners\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub Docs: Self-hosted runners reference<\/a><\/li>\n<li><a href=\"https:\/\/docs.github.com\/en\/actions\/how-tos\/manage-runners\/self-hosted-runners\/add-runners\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub Docs: Adding self-hosted runners<\/a><\/li>\n<li><a href=\"https:\/\/man.openbsd.org\/sshd.8\" target=\"_blank\" rel=\"noopener noreferrer\">OpenBSD manual: sshd(8), AUTHORIZED_KEYS FILE FORMAT<\/a><\/li>\n<li><a href=\"https:\/\/docs.docker.com\/engine\/install\/linux-postinstall\/\" target=\"_blank\" rel=\"noopener noreferrer\">Docker: Linux post-installation steps for Docker Engine<\/a><\/li>\n<li><a href=\"https:\/\/watchtower.nickfedor.com\/v1.14.3\/configuration\/arguments\/\" target=\"_blank\" rel=\"noopener noreferrer\">Watchtower (nicholas-fedor): Arguments<\/a><\/li>\n<li><a href=\"https:\/\/pkg.go.dev\/github.com\/robfig\/cron@v1.2.0\" target=\"_blank\" rel=\"noopener noreferrer\">robfig\/cron v1.2.0: CRON Expression Format<\/a><\/li>\n<li><a href=\"https:\/\/docs.docker.com\/reference\/cli\/docker\/compose\/up\/\" target=\"_blank\" rel=\"noopener noreferrer\">Recreate, volumes, pull policy, wait and remove-orphans<\/a><\/li>\n<li><a href=\"https:\/\/docs.docker.com\/reference\/dockerfile\/#healthcheck\" target=\"_blank\" rel=\"noopener noreferrer\">Health status, intervals and retries<\/a><\/li>\n<li><a href=\"https:\/\/docs.docker.com\/engine\/containers\/start-containers-automatically\/\" target=\"_blank\" rel=\"noopener noreferrer\">Restart policy operates on process exit<\/a><\/li>\n<li><a href=\"https:\/\/docs.github.com\/en\/webhooks\/using-webhooks\/validating-webhook-deliveries\" target=\"_blank\" rel=\"noopener noreferrer\">HMAC SHA256 and constant-time comparison<\/a><\/li>\n<li><a href=\"https:\/\/docs.github.com\/en\/webhooks\/using-webhooks\/best-practices-for-using-webhooks\" target=\"_blank\" rel=\"noopener noreferrer\">Event checks, queue, delivery IDs<\/a><\/li>\n<li><a href=\"https:\/\/docs.github.com\/en\/actions\/concepts\/runners\/private-networking\" target=\"_blank\" rel=\"noopener noreferrer\">Private network access from hosted runners<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/containrrr\/watchtower\" target=\"_blank\" rel=\"noopener noreferrer\">Original project archived December17,2025<\/a><\/li>\n<\/ul>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>How to manage container stacks (Home Assistant, Nextcloud, Python scripts) on a local edge server using GitOps, webhook listeners, and local agents without manual SSH login.<\/p>\n","protected":false},"author":1,"featured_media":13989,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21],"tags":[91106,91144,91145,91113,91181,91237],"class_list":["post-429","post","type-post","status-publish","format-standard","hentry","category-raspberry-pi","tag-automation","tag-devops","tag-docker","tag-homelab","tag-raspberry-pi","tag-self-hosting"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Local CI\/CD for Raspberry Pi: Automating Docker Compose Deployments | Lukas Wojcik<\/title>\n<meta name=\"description\" content=\"Automate Docker Compose deployments on Raspberry Pi: GitOps, webhooks, local agents and CI\/CD pipeline security.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Local CI\/CD for Raspberry Pi: Automating Docker Compose Deployments | Lukas Wojcik\" \/>\n<meta property=\"og:description\" content=\"Automate Docker Compose deployments on Raspberry Pi: GitOps, webhooks, local agents and CI\/CD pipeline security.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-08-31T05:45:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-10-23T23:39:04+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-429-local-ci-cd-raspberry-pi-automating--g.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Lukas Wojcik\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Lukas Wojcik\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"2 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/raspberry-pi\\\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/raspberry-pi\\\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\\\/\"},\"author\":{\"name\":\"Lukas Wojcik\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"headline\":\"Local CI\\\/CD for Raspberry Pi: Automating Docker Compose Deployments\",\"datePublished\":\"2026-08-31T05:45:00+00:00\",\"dateModified\":\"2026-10-23T23:39:04+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/raspberry-pi\\\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\\\/\"},\"wordCount\":1010,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/raspberry-pi\\\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/hero-429-local-ci-cd-raspberry-pi-automating--g.png\",\"keywords\":[\"Automation\",\"DevOps\",\"Docker\",\"Homelab\",\"Raspberry Pi\",\"Self-Hosting\"],\"articleSection\":[\"Raspberry PI\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/raspberry-pi\\\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/raspberry-pi\\\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/raspberry-pi\\\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\\\/\",\"name\":\"Local CI\\\/CD for Raspberry Pi: Automating Docker Compose Deployments | Lukas Wojcik\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/raspberry-pi\\\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/raspberry-pi\\\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/hero-429-local-ci-cd-raspberry-pi-automating--g.png\",\"datePublished\":\"2026-08-31T05:45:00+00:00\",\"dateModified\":\"2026-10-23T23:39:04+00:00\",\"description\":\"Automate Docker Compose deployments on Raspberry Pi: GitOps, webhooks, local agents and CI\\\/CD pipeline security.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/raspberry-pi\\\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/raspberry-pi\\\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/raspberry-pi\\\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/hero-429-local-ci-cd-raspberry-pi-automating--g.png\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/hero-429-local-ci-cd-raspberry-pi-automating--g.png\",\"width\":1200,\"height\":630,\"caption\":\"Local CI\\\/CD for Raspberry Pi: Automating Docker Compose Deployments\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/raspberry-pi\\\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Local CI\\\/CD for Raspberry Pi: Automating Docker Compose Deployments\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"Lukas Wojcik\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"Lukas Wojcik\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Local CI\/CD for Raspberry Pi: Automating Docker Compose Deployments | Lukas Wojcik","description":"Automate Docker Compose deployments on Raspberry Pi: GitOps, webhooks, local agents and CI\/CD pipeline security.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/","og_locale":"en_US","og_type":"article","og_title":"Local CI\/CD for Raspberry Pi: Automating Docker Compose Deployments | Lukas Wojcik","og_description":"Automate Docker Compose deployments on Raspberry Pi: GitOps, webhooks, local agents and CI\/CD pipeline security.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/","og_site_name":"Lukas Wojcik - Blog","article_published_time":"2026-08-31T05:45:00+00:00","article_modified_time":"2026-10-23T23:39:04+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-429-local-ci-cd-raspberry-pi-automating--g.png","type":"image\/png"}],"author":"Lukas Wojcik","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Lukas Wojcik","Est. reading time":"2 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/#article","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/"},"author":{"name":"Lukas Wojcik","@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"headline":"Local CI\/CD for Raspberry Pi: Automating Docker Compose Deployments","datePublished":"2026-08-31T05:45:00+00:00","dateModified":"2026-10-23T23:39:04+00:00","mainEntityOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/"},"wordCount":1010,"commentCount":0,"publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-429-local-ci-cd-raspberry-pi-automating--g.png","keywords":["Automation","DevOps","Docker","Homelab","Raspberry Pi","Self-Hosting"],"articleSection":["Raspberry PI"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/","name":"Local CI\/CD for Raspberry Pi: Automating Docker Compose Deployments | Lukas Wojcik","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/#primaryimage"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-429-local-ci-cd-raspberry-pi-automating--g.png","datePublished":"2026-08-31T05:45:00+00:00","dateModified":"2026-10-23T23:39:04+00:00","description":"Automate Docker Compose deployments on Raspberry Pi: GitOps, webhooks, local agents and CI\/CD pipeline security.","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/#primaryimage","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-429-local-ci-cd-raspberry-pi-automating--g.png","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-429-local-ci-cd-raspberry-pi-automating--g.png","width":1200,"height":630,"caption":"Local CI\/CD for Raspberry Pi: Automating Docker Compose Deployments"},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/raspberry-pi\/local-ci-cd-for-raspberry-pi-automating-docker-compose-deployments\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Local CI\/CD for Raspberry Pi: Automating Docker Compose Deployments"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"Lukas Wojcik","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"Lukas Wojcik"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/429","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=429"}],"version-history":[{"count":6,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/429\/revisions"}],"predecessor-version":[{"id":23027,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/429\/revisions\/23027"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media\/13989"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=429"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/categories?post=429"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=429"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}