How to Deploy Woodpecker CI 1.0.3 in Docker Swarm Behind Caddy v2.7.5
Woodpecker is a simple, highly extensible CI engine. It focuses on executing pipelines inside containers. If you use containers in your daily workflow, you'll love Woodpecker.

Woodpecker is a simple CI engine with tremendous extensibility. It focuses entirely on executing pipelines inside containers. If you are using containers in your daily workflow, you will love Woodpecker.
In this post, I am going to show you how to deploy Woodpecker CI 1.0.3, a container-native continuous delivery platform, into our Docker Swarm Cluster using Docker Compose, sitting securely behind Caddy v2.7.5.
If you want to learn more about Woodpecker CI, please check out the links below:
Let’s start with the actual deployment.
Prerequisites
Please ensure you fulfill the following requirements before proceeding with the deployment:
- A Docker Swarm Cluster configured with GlusterFS for persistent storage.
- Caddy deployed as the ingress reverse proxy to expose microservices externally.
Introduction to Woodpecker CI
Woodpecker CI is a simple CI engine born as a fork of Drone CI. It focuses on executing pipelines inside isolated Docker containers that are automatically downloaded at runtime.
Woodpecker uses a pipeline file (.woodpecker.yml) that you commit directly to your Git repository. This file contains a single pipeline or multiple pipelines instructing the server on how to build an application and publish the Docker container to a registry of your choice.
Pipeline steps can be named whatever you like. You can run any command in the commands section, as steps are simply containers and file changes are incremental.
Persisting Woodpecker Data with GlusterFS
Containers are fast to deploy and make efficient use of system resources. However, their filesystems are ephemeral. If a container restarts, local data is lost.
To overcome this, we use GlusterFS. I previously set up a replicated GlusterFS volume to ensure data is mirrored across all nodes in the cluster.

The volume is mounted across all nodes. When data is written to the
/mntpartition, it is instantly replicated.
If any node fails, the application automatically restarts on another node without losing data. This is the primary advantage of a replicated volume.
For disaster recovery, we need to persist the /var/lib/woodpecker/ directory.
Create the required persistent folder in the /mnt directory:
cd /mnt
sudo mkdir -p woodpeckerdata
Watch the video below for a complete guide on setting up a GlusterFS Replicated Volume.
Prepare the Deployment Environment
We will use Docker Compose to define the deployment environment.
Navigate to the /opt directory on your Swarm manager node and create the configuration directory for Woodpecker:
cd /opt
sudo mkdir -p woodpecker
cd woodpecker
sudo touch woodpecker.yml
Woodpecker Docker Compose Configuration
Open woodpecker.yml using your editor:
sudo nano woodpecker.yml
Paste the following Docker Compose configuration. I am utilizing SQLite as a backend database for simplicity. Note that I am using Gitea as my Git provider in the environment variables.
version: "3.7"
services:
woodpecker-server:
image: woodpeckerci/woodpecker-server:latest-alpine
volumes:
- /mnt/woodpeckerdata:/var/lib/woodpecker/
environment:
- WOODPECKER_HOST=[https://woodpecker.example.com](https://woodpecker.example.com)
- WOODPECKER_GITEA=true
- WOODPECKER_GITEA_CLIENT=gitea-client-id
- WOODPECKER_GITEA_SECRET=gitea-client-secret
- WOODPECKER_GITEA_URL=[https://gitea.example.com](https://gitea.example.com)
- WOODPECKER_AGENT_SECRET=agent-secret
- WOODPECKER_ADMIN=username
- WOODPECKER_REPO_OWNERS=username
networks:
- caddy
deploy:
placement:
constraints: [node.role == worker]
replicas: 1
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
woodpecker-agent:
image: woodpeckerci/woodpecker-agent:latest-alpine
command: agent
depends_on:
- woodpecker-server
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WOODPECKER_SERVER=woodpecker-server:9000
- WOODPECKER_AGENT_SECRET=agent-secret
- WOODPECKER_MAX_PROCS=10
- WOODPECKER_BACKEND=docker
- WOODPECKER_HOST=[https://woodpecker.example.com](https://woodpecker.example.com)
networks:
- caddy
deploy:
placement:
constraints: [node.role == worker]
replicas: 1
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
volumes:
woodpeckerdata:
driver: "local"
networks:
caddy:
external: true
attachable: true
Watch the video below to see a walkthrough of deploying Woodpecker CI in a Docker Swarm Cluster.
Caddyfile Configuration
Here is the production-ready Caddyfile required to expose Woodpecker securely. Learn more about writing Caddyfiles here.
{
email you@example.com
default_sni woodpecker
cert_issuer acme
acme_ca [https://acme-v02.api.letsencrypt.org/directory](https://acme-v02.api.letsencrypt.org/directory)
servers {
metrics
protocols h1 h2c h3
strict_sni_host on
trusted_proxies cloudflare {
interval 12h
timeout 15s
}
}
}
woodpecker.example.com {
log {
output file /var/log/caddy/woodpecker.log {
roll_size 20mb
roll_keep 2
roll_keep_for 6h
}
format console
level error
}
encode gzip zstd
reverse_proxy woodpecker-server:8000
}
Full Stack Deployment (Combined)
If you prefer to deploy Caddy and Woodpecker together in a single stack, here is the combined docker-compose.yml file.
version: "3.7"
services:
caddy:
image: tuneitme/caddy
ports:
- "80:80"
- "443:443"
networks:
- caddy
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- /mnt/caddydata:/data
- /mnt/caddyconfig:/config
- /mnt/caddylogs:/var/log/caddy
deploy:
placement:
constraints:
- node.role == manager
replicas: 1
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
woodpecker-server:
image: woodpeckerci/woodpecker-server:latest-alpine
volumes:
- /mnt/woodpeckerdata:/var/lib/woodpecker/
environment:
- WOODPECKER_HOST=[https://woodpecker.example.com](https://woodpecker.example.com)
- WOODPECKER_GITEA=true
- WOODPECKER_GITEA_CLIENT=gitea-client-id
- WOODPECKER_GITEA_SECRET=gitea-client-secret
- WOODPECKER_GITEA_URL=[https://gitea.example.com](https://gitea.example.com)
- WOODPECKER_AGENT_SECRET=agent-secret
- WOODPECKER_ADMIN=username
- WOODPECKER_REPO_OWNERS=username
networks:
- caddy
deploy:
placement:
constraints: [node.role == worker]
replicas: 1
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
woodpecker-agent:
image: woodpeckerci/woodpecker-agent:latest-alpine
command: agent
depends_on:
- woodpecker-server
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WOODPECKER_SERVER=woodpecker-server:9000
- WOODPECKER_AGENT_SECRET=agent-secret
- WOODPECKER_MAX_PROCS=10
- WOODPECKER_BACKEND=docker
- WOODPECKER_HOST=[https://woodpecker.example.com](https://woodpecker.example.com)
networks:
- caddy
deploy:
placement:
constraints: [node.role == worker]
replicas: 1
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
volumes:
caddydata:
driver: "local"
caddyconfig:
driver: "local"
caddylogs:
driver: "local"
woodpeckerdata:
driver: "local"
networks:
caddy:
external: true
attachable: true
I used a custom Caddy Docker image bundled with specific plugins (like Cloudflare DNS) for my environment: Tuneit Caddy Docker Image
Deploy Woodpecker to Docker Swarm
Now it’s time to deploy our woodpecker.yml using the following command:
docker stack deploy --compose-file woodpecker.yml woodpecker
In Docker Swarm, whatever you deploy via compose is called a “stack,” and it contains multiple “services.”
Check the status of the deployment to ensure it scheduled properly:
docker stack ps woodpecker
Check the Woodpecker server logs to ensure successful startup:
docker service logs woodpecker_woodpecker-server
You will notice that Caddy automatically intercepts the traffic, redirects it to HTTPS, and provisions Let’s Encrypt certificates.
Access and Configure Woodpecker
Open your browser and navigate to woodpecker.example.com (ensure you replace example.com with your actual domain). It will securely redirect to https://woodpecker.example.com/welcome.
Ensure you have configured a DNS A-Record or CNAME pointing woodpecker.example.com to your Swarm ingress load balancer.
Reference Images from the Deployment:
















The deployment of Woodpecker CI behind Caddy in our Docker Swarm cluster is complete!
If you enjoyed this tutorial, please share your thoughts in the comments below. It helps me bring more self-hosted open-source content to the community.
Stay tuned for more deployment guides!
Continue reading

How to Deploy Drone 2.16.0 in Docker Swarm Behind Caddy v2.6.4
Drone is a self-service, container-native Continuous Integration and Delivery platform designed for busy development teams.

How to Deploy Gitea 1.18.0 in Docker Swarm Behind Caddy v2.6.2
Git with a cup of tea. Learn how to deploy Gitea, a painless, self-hosted, lightweight code hosting solution written in Go, to a Docker Swarm cluster.

How to Deploy Jenkins v2.426.2 in Docker Swarm Behind Caddy v2.7.6
Jenkins is a popular open-source automation server written in Java. Learn how to deploy a highly available CI/CD pipeline engine using Docker Swarm and Caddy.