DockerSelf-Hosting

Woodpecker CI with Docker: simple CI/CD for Forgejo and Gitea (Linux, Windows, Mac)

Before you start

  • 20 min read

Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#

Woodpecker CI is a free, self-hosted CI/CD engine: you push code to your own Forgejo or Gitea, and Woodpecker runs your tests and builds, each step in its own Docker container, then reports the result back.

Woodpecker CI official logo

Logo: the official Woodpecker project (github.com/woodpecker-ci/woodpecker, docs/static/img/logo.svg at v3.20.1; the README puts the docs folder under CC BY-SA 4.0 and the code under Apache 2.0)

Pipelines are YAML files in your repository (.woodpecker/*.yaml). It suits lint, test and build runs on your own hardware. The project is alive: not archived, not renamed, and v3.20.1 came out on 29 October 2026.

WhatValue (official docs and releases, read November 2026)
Current versionWoodpecker v3.20.1, released 29 October 2026 (latest)
Images to pinwoodpeckerci/woodpecker-server:v3.20.1 and woodpeckerci/woodpecker-agent:v3.20.1 (tags checked on Docker Hub). There is no latest tag
LicenceApache 2.0 (docs folder: CC BY-SA 4.0)
Time neededAbout 30 minutes with a Forgejo you already run (our estimate, not measured)
RAMREADME: about 100 MB for the server and 30 MB for the agent when idle
Ports8000/TCP web UI and webhooks; 9000/TCP gRPC between agent and server (we keep it inside Docker)
Official docswoodpecker-ci.org/docs and the repository

Screenshot of the Woodpecker Add repository page: a list of repositories from the forge, each with an Already enabled label

Image: the official Woodpecker docs (first pipeline page). It shows the project's own test instance, so your list will hold your repositories.

At a glance:

  • Three parts: a server (web UI, webhooks, queue), an agent (runs the steps) and your forge.
  • Login through your forge: you create an OAuth app in Forgejo; there is no separate Woodpecker password.
  • Docker socket: the agent needs /var/run/docker.sock. That is close to root on the host; read the security section.
  • Platforms: Linux; Windows through WSL 2 with Docker Desktop, per the docs; no Docker backend on macOS.
  • Also for Gitea: same setup with the WOODPECKER_GITEA_* variables (note after Step 3).

How it fits together#

You push a commit, Forgejo calls Woodpecker over a webhook, the server queues the work, and the agent runs each step as a container.

Three-lane diagram: you push a commit to Forgejo; Forgejo sends a webhook through an optional HTTPS proxy to the Woodpecker server on port 8000; the server hands the job to the agent over gRPC on port 9000; the agent starts step containers through the Docker socket; the server writes its SQLite database to a data folder on the host

Drawn for this guide from the official Docker Compose docs. The dashed HTTPS proxy is optional for a test but needed for a real login address.

Before you start#

You need a running Forgejo or Gitea that you can administer, and a machine with Docker. If you have no Forgejo yet, follow our Forgejo guide first. Forgejo has its own Actions runner; Woodpecker is the alternative when you prefer a separate, simple CI.

SystemServer and agentOur advice
LinuxSupported (Docker backend)Use this
WindowsDocs: Docker backend "works through WSL2 with Docker Desktop"Work inside a WSL 2 Linux shell
MacNo Docker backend for the native agent in the docsUse a Linux machine; the Mac is a client (browser, Git)

Check Docker (Linux, WSL 2, or PowerShell and Terminal with Docker Desktop). To install it on Ubuntu, see our Docker installation guide:

Run in terminalbash
docker --version
docker compose version

Checklist:

  • [ ] Forgejo or Gitea reachable by its address from the Woodpecker machine, with HTTPS if possible
  • [ ] Admin rights on that Forgejo (for the webhook setting)
  • [ ] A domain name for Woodpecker (for example woodpecker.<your-domain>) with a DNS record to the server
  • [ ] Ports 80 and 443 free for the HTTPS section
  • [ ] A backup of anything important on the machine

Tested by DIY Lab Hub: not yet. The commands come from the official docs, Dockerfiles and release files at v3.20.1, read in November 2026. We did not run them.

Step 1: Create the folders and the secrets#

Woodpecker keeps its SQLite database in /var/lib/woodpecker. The docs call it "the most important part to be persisted", because it holds all users and repository information. We use ~/docker_volumes/woodpecker, as all DIY Lab Hub guides do.

Linux#

The server image runs as user ID 1000 (the Dockerfile has USER woodpecker), so the data folder must belong to 1000. Skipping this causes the disk I/O error: permission denied in the mistakes table.

Run in terminalbash
mkdir -p ~/docker_volumes/woodpecker/{data,agent,backups}
sudo chown 1000:1000 ~/docker_volumes/woodpecker/data
cd ~/docker_volumes/woodpecker
openssl rand -hex 32

The last command prints the shared secret for server and agent (the docs use exactly this command). Copy it; you need it in Step 3.

Windows (PowerShell)#

Not tested by us. Our advice, not from the docs: use the Linux commands in a WSL 2 shell, so the data stays on a Linux file system. In PowerShell, create the folders and the secret like this:

Codepowershell
New-Item -ItemType Directory -Force "$HOME\docker_volumes\woodpecker\data","$HOME\docker_volumes\woodpecker\agent","$HOME\docker_volumes\woodpecker\backups"
$b = New-Object byte[] 32
[Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($b)
($b | ForEach-Object { $_.ToString('x2') }) -join ''

Mac (Terminal)#

macOS is not a documented Docker-backend platform and we did not test it. If you try, use the Linux commands without sudo chown:

Run in terminalbash
mkdir -p ~/docker_volumes/woodpecker/{data,agent,backups}
cd ~/docker_volumes/woodpecker
openssl rand -hex 32

Check it worked. The folders exist and you hold a 64-character secret:

Run in terminalbash
ls -ld ~/docker_volumes/woodpecker/data

Tip: Never paste that secret into a chat, an issue or a screenshot. Anyone with it can register an agent that runs your pipelines.

Step 2: Register an OAuth app in Forgejo#

Woodpecker logs you in through Forgejo, so Forgejo must know about it first.

Linux#

In a browser, sign in to Forgejo and open the OAuth applications settings of your user (the Woodpecker docs give the path https://forgejo.<host>/user/settings/; look for Applications). Create a new OAuth2 application:

FieldValue
NameWoodpecker CI (any name)
Redirect URIhttps://woodpecker.<your-domain>/authorize

The docs stress that the callback URL must match your http(s) scheme and hostname exactly, with /authorize as the path. Copy the client ID and the client secret now.

The docs also describe a system-wide OAuth app for admins at https://forgejo.<host>/admin/settings/applications.

Then let Forgejo call Woodpecker. By default Forgejo's webhooks only reach public addresses (ALLOWED_HOST_LIST defaults to external). Allow your case in the Forgejo environment, then restart Forgejo:

Woodpecker runs...Add to Forgejo's environment:
On a public domainNothing. The default works
On the same host (docs example)FORGEJO__webhook__ALLOWED_HOST_LIST=external,loopback
On a private LAN addressFORGEJO__webhook__ALLOWED_HOST_LIST=external,private

Forgejo's cheat sheet defines private as the RFC 1918 and related ranges.

Windows (PowerShell)#

Same steps in a browser. Add the Forgejo variable on the machine where Forgejo runs.

Mac (Terminal)#

Same as Windows: browser steps only.

Check it worked. The OAuth app appears in your Forgejo application list, and you hold the client ID and the client secret.

Tip: Write the redirect URI with the final HTTPS address from the start. If you change the domain later, edit the OAuth app too, or the login fails.

Step 3: Write the Compose file and start Woodpecker#

This is the official Docker Compose example with changes: the Forgejo variables instead of GitHub, the images pinned to v3.20.1 (the docs example uses the rolling v3), bind-mounted folders, restart: unless-stopped, the web port on 127.0.0.1 only, and WOODPECKER_OPEN=false. The docs example sets WOODPECKER_OPEN=true, which lets anyone who can sign in to your forge register.

Linux#

Create the secrets file. Replace the placeholders; WOODPECKER_ADMIN is your own Forgejo login name:

Run in terminalbash
cd ~/docker_volumes/woodpecker
cat > .env << EOF
WOODPECKER_HOST=https://woodpecker.<your-domain>
WOODPECKER_ADMIN=<your-forgejo-username>
WOODPECKER_FORGEJO_URL=https://git.<your-domain>
WOODPECKER_FORGEJO_CLIENT=<client-id-from-step-2>
WOODPECKER_FORGEJO_SECRET=<client-secret-from-step-2>
WOODPECKER_AGENT_SECRET=<secret-from-step-1>
EOF
chmod 600 .env

Then the Compose file:

Run in terminalbash
cat > docker-compose.yml << 'EOF'
services:
  woodpecker-server:
    image: woodpeckerci/woodpecker-server:v3.20.1
    container_name: woodpecker-server
    restart: unless-stopped
    ports:
      - "127.0.0.1:8000:8000"
    volumes:
      - ./data:/var/lib/woodpecker
    environment:
      - WOODPECKER_OPEN=false
      - WOODPECKER_ADMIN=${WOODPECKER_ADMIN}
      - WOODPECKER_HOST=${WOODPECKER_HOST}
      - WOODPECKER_FORGEJO=true
      - WOODPECKER_FORGEJO_URL=${WOODPECKER_FORGEJO_URL}
      - WOODPECKER_FORGEJO_CLIENT=${WOODPECKER_FORGEJO_CLIENT}
      - WOODPECKER_FORGEJO_SECRET=${WOODPECKER_FORGEJO_SECRET}
      - WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}

  woodpecker-agent:
    image: woodpeckerci/woodpecker-agent:v3.20.1
    container_name: woodpecker-agent
    command: agent
    restart: unless-stopped
    depends_on:
      - woodpecker-server
    volumes:
      - ./agent:/etc/woodpecker
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - WOODPECKER_SERVER=woodpecker-server:9000
      - WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
EOF
docker compose up -d
docker compose ps
docker compose logs --tail=50 woodpecker-server

Port 9000 is not published: the agent reaches the server over the Compose network. Compose reads .env automatically, which keeps secrets out of the YAML.

Windows (PowerShell)#

Create the same two files in $HOME\docker_volumes\woodpecker with the contents of the Linux blocks, for example with notepad .env and notepad docker-compose.yml. Then, in that folder:

Run in terminalpowershell
cd $HOME\docker_volumes\woodpecker
docker compose up -d
docker compose ps
docker compose logs --tail=50 woodpecker-server

Not tested by us. If you run the stack in WSL 2, use the Linux commands.

Mac (Terminal)#

Same commands as Linux, minus the chown from Step 1. This is not a documented platform for the Docker backend; we did not test it.

Check it worked. docker compose ps lists both containers as running; the server image has a built-in health check (woodpecker-server ping), so its status should become healthy. The docs publish no expected log text, so read the log for errors:

Run in terminalbash
curl -I http://127.0.0.1:8000/

Any HTTP status line means the web UI answers.

Tip: Change the tag yourself when you update. A moving tag such as v3 can pull in a new minor release on the next docker compose pull.

Gitea instead of Forgejo. The docs have a Gitea page with the same pattern: replace the four Forgejo variables with WOODPECKER_GITEA=true, WOODPECKER_GITEA_URL, WOODPECKER_GITEA_CLIENT and WOODPECKER_GITEA_SECRET. Register the OAuth app at https://gitea.<host>/user/settings/ with the same /authorize redirect.

Put it behind HTTPS (with or without Traefik)#

Woodpecker must know its own public address, and the OAuth redirect needs it, so do this before your first login. Both routes need a DNS record for woodpecker.<your-domain> pointing to the server. A home-only setup works with tls internal; read the self-signed certificate row in the mistakes table first.

With Traefik#

On a server that already runs Traefik v3 with the Docker provider, add the proxy network and labels to the server, and drop its host port.

Create the network once if it is missing:

Run in terminalbash
docker network create proxy

In docker-compose.yml, change the woodpecker-server service like this (the default network stays, so the agent still reaches the server), and add the network block at the end of the file. The entrypoint name https follows our other guides; use yours:

File contentyaml
  woodpecker-server:
    # image, restart, volumes and environment stay as in Step 3
    networks:
      - default
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.woodpecker.rule=Host(`woodpecker.<your-domain>`)"
      - "traefik.http.routers.woodpecker.entrypoints=https"
      - "traefik.http.routers.woodpecker.tls=true"
      - "traefik.http.services.woodpecker.loadbalancer.server.port=8000"

networks:
  proxy:
    external: true

Remove the ports: lines of the server, then run docker compose up -d.

Check it worked. curl -I https://woodpecker.<your-domain>/ returns an HTTP status line over HTTPS and the page opens with a valid certificate.

Without Traefik: Caddy#

Choose this if you have no Traefik. Caddy gets and renews its certificates for you. Put the server on the shared proxy network exactly as in the Traefik block (the networks: lines, without the labels), then create the Caddy folders:

Run in terminalbash
docker network create proxy
mkdir -p ~/docker_volumes/caddy/{conf,data,config}
cd ~/docker_volumes/caddy

Save this as ~/docker_volumes/caddy/docker-compose.yml. The image is caddy:2.11.8, the newest tag on Docker Hub when we checked (Caddy v2.11.9 was released on the day of writing and had no image yet). Caddy's Docker docs say to mount the conf folder, not a single file:

File contentyaml
services:
  caddy:
    image: caddy:2.11.8
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80/tcp"
      - "443:443/tcp"
      - "443:443/udp"
    volumes:
      - ./conf:/etc/caddy
      - ./data:/data
      - ./config:/config
    networks:
      - proxy

networks:
  proxy:
    external: true

Save one of these as ~/docker_volumes/caddy/conf/Caddyfile. For a public domain, Caddy asks Let's Encrypt for a certificate by itself:

File contenttext
woodpecker.example.com {
    reverse_proxy woodpecker-server:8000
}

For a home-only name, use tls internal, Caddy's own local certificate:

File contenttext
woodpecker.home.arpa {
    tls internal
    reverse_proxy woodpecker-server:8000
}

Start Caddy, then recreate Woodpecker with the changed file:

Run in terminalbash
cd ~/docker_volumes/caddy && docker compose up -d
cd ~/docker_volumes/woodpecker && docker compose up -d

For the home-only certificate, copy Caddy's root certificate out of its data folder and install it as a trusted root on each device you browse from:

Run in terminalbash
docker exec caddy find /data -name root.crt
docker cp caddy:<path-from-find> ./caddy-root.crt

On Windows import it into "Trusted Root Certification Authorities"; on macOS use Keychain Access and "Always Trust"; on Linux use your distribution's CA store (standard system steps, not from the Woodpecker or Caddy docs). Windows and Mac only browse here; run the Caddy commands on the server.

Check it worked. curl -I https://woodpecker.example.com/ returns an HTTP status line. For a home-only name, the padlock appears only after the root is trusted on that device.

Tip: Let's Encrypt needs the DNS record to point at your server and ports 80 and 443 to reach it. A certificate error usually means the name in the Caddyfile differs from the DNS record. See our Caddy guide for more.

Step 4: Log in and activate a repository#

You sign in with your Forgejo account, then tell Woodpecker which repository to watch.

Linux#

  1. Open https://woodpecker.<your-domain> and choose to log in. Forgejo asks you to authorise the app.
  2. Back in Woodpecker, open Repositories and Add repository, then enable your repository.
  3. Open the settings (gear icon) and go to Agents. Your agent should be listed as connected (the docs show this screen as "Settings -> Agents").

Enabling a repository needs admin rights on it, because Woodpecker adds a webhook. Your name in WOODPECKER_ADMIN makes you a server admin at first sign-in even with registration closed (we read the login code at v3.20.1).

Windows (PowerShell)#

Same steps in a browser. A reachability test:

Run in terminalpowershell
curl.exe -I https://woodpecker.<your-domain>/

Mac (Terminal)#

Same steps; the test in Terminal:

Run in terminalbash
curl -I https://woodpecker.<your-domain>/

Check it worked. You see the repository list, the agent shows as connected, and Forgejo lists a new webhook under the repository's Webhooks settings.

Tip: Other people on your Forgejo cannot sign in while WOODPECKER_OPEN=false. Add them by letting an admin enable registration briefly, or keep it closed for a private CI.

Step 5: Write your first pipeline and add a secret#

A pipeline is a YAML file in the repository. This is the docs' first example: two steps in two containers, run on a push to main.

Linux#

In your repository, create .woodpecker/my-first-workflow.yaml:

File contentyaml
when:
  - event: push
    branch: main

steps:
  - name: build
    image: debian
    commands:
      - echo "This is the build step"
      - echo "some-data" > some-file.txt
  - name: a-test-step
    image: alpine
    commands:
      - echo "Testing ..."
      - cat some-file.txt

Commit and push it:

Run in terminalbash
mkdir -p .woodpecker
# save the YAML above as .woodpecker/my-first-workflow.yaml, then:
git add .woodpecker
git commit -m "Add first Woodpecker pipeline"
git push

The pipeline appears in Woodpecker, with an extra clone step before yours that fetches the repository into a shared workspace.

To use a secret, add it in the repository's settings (Secrets) in the web UI, then pass it into a step:

File contentyaml
steps:
  - name: use-secret
    image: alpine
    environment:
      TOKEN_ENV:
        from_secret: secret_token
    commands:
      - test -n "$$TOKEN_ENV" && echo "secret is set"

The double $$ matters: Woodpecker evaluates expressions before the pipeline starts, and the docs say secrets in expressions must be escaped that way.

Windows (PowerShell)#

Create the file in your repository folder, then commit and push:

Codepowershell
New-Item -ItemType Directory -Force .woodpecker
notepad .woodpecker\my-first-workflow.yaml
git add .woodpecker
git commit -m "Add first Woodpecker pipeline"
git push

Paste the YAML from the Linux block into Notepad and save it.

Mac (Terminal)#

Same commands as Linux, with your preferred editor for the file:

Run in terminalbash
mkdir -p .woodpecker
git add .woodpecker
git commit -m "Add first Woodpecker pipeline"
git push

Check it worked. In the Woodpecker web UI, open the repository's Pipelines: the run shows clone, build and a-test-step as successful, and the test step prints some-data. Forgejo also shows the commit status.

Tip: woodpecker-cli exec .woodpecker/my-first-workflow.yaml tries a workflow before you push it (from the docs; we did not test it).

Security and testing#

The agent starts step containers by talking to the Docker daemon through /var/run/docker.sock. Anyone who controls that socket controls the host. The pipeline files come from your repositories, so the real question is who can change them.

ControlHow
Who can log inKeep WOODPECKER_OPEN=false. For a shared forge, WOODPECKER_ORGS limits sign-in to listed organizations
Repository stays untrustedOnly instance admins can make a repository "trusted". The docs warn that trusted pipelines can allow privileged, volumes and devices, and "can at least compromise the hosts agents run on". Do not enable it
Pull requests from forksBy default they wait for approval. Do not approve one you have not read: an approved pipeline gets the current secrets
SecretsNot exposed to pull requests by default. Enabling the pull_request event for a secret exposes it to anyone who can open one
Agent registrationSet WOODPECKER_DISABLE_USER_AGENT_REGISTRATION=true if you have global secrets and do not trust your users
Webhook URLKeep it private. If it leaks, use "Repair repository" to rotate the secret
gRPC (port 9000)Served without TLS. Keep it inside Docker. A remote agent needs a TLS proxy and WOODPECKER_GRPC_SECURE=true
.env filechmod 600; it holds the OAuth secret and the agent secret

To limit the risk, run the agent on its own machine or VM (our suggestion, not from the docs).

Quick tests on Linux after each change:

Run in terminalbash
cd ~/docker_volumes/woodpecker
docker compose ps
docker compose logs --tail=100 woodpecker-server
docker compose logs --tail=100 woodpecker-agent
curl -I https://woodpecker.<your-domain>/

Windows and Mac can run the same curl check in PowerShell (curl.exe -I) or Terminal.

Common mistakes#

These come from the official docs, the GitHub issue tracker and common AI answers.

MistakeWhy it happensDo this instead
Copying WOODPECKER_OPEN=true from the docs exampleThe docs example allows registration; the setting is documented as "Enable to allow user registration"Use WOODPECKER_OPEN=false and set WOODPECKER_ADMIN to your own login. An admin listed there can still register at first sign-in
A data folder owned by rootThe server image runs as user 1000. Issues #5330 and #6938 report disk I/O error: permission denied; in #6938 chown -R 1000:1000 data got a fresh install to startsudo chown 1000:1000 ~/docker_volumes/woodpecker/data before the first start
Using woodpecker-server:latestThe docs say "No latest tag exists" to avoid accidental major upgradesPin v3.20.1, or accept minor updates with v3
A redirect URI that does not matchThe docs: scheme and hostname must match exactly, with /authorize. Issue #1560 describes a login that times out on that stepUse https://woodpecker.<your-domain>/authorize in the OAuth app and the same host in WOODPECKER_HOST
Forgejo behind a self-signed certificateIssue #6693 reports x509: certificate signed by unknown authority when Woodpecker calls ForgejoUse a public domain with a Let's Encrypt certificate. The maintainer's answer was WOODPECKER_FORGEJO_SKIP_VERIFY=true, which switches verification off, so treat it as a last resort
Webhooks never arriveForgejo only calls public hosts by default (ALLOWED_HOST_LIST=external)Add loopback for the same host, or private for a LAN address (Step 2)
Adding users by hand with registration closedIssue #6769 (closed, fix in #6823) reports unusable users with forge_id=0Let the user sign in once while registration is open, and stay on a current release
WOODPECKER_ADMIN not matching the loginIssue #6231 reports no admin rights; the list is read at startCopy the exact Forgejo login name and restart the server
AI assistants: writing woodpecker-server:latest, WOODPECKER_GITEA_* for a Forgejo, or a Drone-style file nameMany examples are old or for another forgeUse the pinned tag, the WOODPECKER_FORGEJO_* variables from the Forgejo page, and .woodpecker/*.yaml as in the docs

Tip: Before running a compose file from a forum or a chatbot, compare its image tags and variable names with the official Docker Compose page.

Tip: After every change read docker compose logs --tail=100 woodpecker-server; login and migration errors print there.

Tip: If the clone step fails, the docs suggest skip_clone: true plus a step that pings and fetches from your Git server, so you can test the network from inside a step container.

Troubleshooting#

ProblemSolution
could not migrate datastore ... disk I/O error: permission deniedFix the data folder owner: sudo chown 1000:1000 ~/docker_volumes/woodpecker/data (issues #5330, #6938)
Login page shows "The registration is closed"The user is not in WOODPECKER_ADMIN and WOODPECKER_OPEN=false. Add the user or enable registration for a moment
x509: certificate signed by unknown authorityWoodpecker does not trust Forgejo's certificate. Use a public certificate, or see the mistakes table
Pipeline never starts after a pushCheck Forgejo's webhook deliveries; set ALLOWED_HOST_LIST (Step 2)
Run stays "pending"No agent is online, or the agent is busy: WOODPECKER_MAX_WORKFLOWS defaults to 1
Agent does not connectSecrets differ, or WOODPECKER_SERVER is wrong. It must be woodpecker-server:9000 inside Compose
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sockOn SELinux systems add :z to the socket mount, as the docs describe
fatal: could not read Username for 'https://<url>': No such device or addressSet WOODPECKER_AUTHENTICATE_PUBLIC_REPOS=true for internal repositories, then test the network from a step (docs troubleshooting)
Features work badly against ForgejoThe docs warn that Forgejo's API page size must not be below 50 (cheat sheet, [api] section)
Disk fills upThe agent never removes images. Clean up on the host (see Performance)

With Forgejo in a container on the same host, the docs say to set WOODPECKER_BACKEND_DOCKER_NETWORK=<forgejo-network-name> on the agent, or to go through the docker0 gateway (usually 172.17.0.1). They warn that step containers of different pipelines can then reach each other.

Known bugs (open, November 2026)#

  • Issue #7472: the Docker backend can report a detached service as failed ("no such container") when the workflow teardown removes it first.
  • Issue #7110: the agent's retry window counts from the start of a long poll, so the first error after an idle period may give up without a retry.
  • Issue #7085: log lines can be dropped silently when a log call fails.

Run docker compose ps, docker compose logs --tail=200 woodpecker-server and docker inspect woodpecker-server first.

Updates and backups#

Update. Read the release notes, change both tags in docker-compose.yml, back up, then:

Run in terminalbash
cd ~/docker_volumes/woodpecker
docker compose pull
docker compose up -d
docker compose logs --tail=100 woodpecker-server

The docs say new versions upgrade the database automatically unless the release notes say otherwise. Issue #6938 shows an upgrade from 3.16 to 3.17 that failed on a large database, so back up first.

Back up. Woodpecker does not back up its database. Stop the stack, archive the data and agent folders and the .env, and start again:

Run in terminalbash
cd ~/docker_volumes/woodpecker
docker compose down
tar -czf "backups/woodpecker-$(date +%F).tar.gz" data agent .env
chmod 600 backups/woodpecker-*.tar.gz
docker compose up -d

The archive holds secrets; never put it in a shared folder. To run it nightly at 03:00 on Linux or Mac, add a crontab entry that runs the same commands. Keep a copy on another machine.

Restore test. Unpack into a new empty folder, never over the live one:

Run in terminalbash
mkdir -p /tmp/restore-test && tar -xzf backups/woodpecker-<date>.tar.gz -C /tmp/restore-test
ls -la /tmp/restore-test/data

Windows (PowerShell)#

If the stack runs in WSL 2, run the backup there. Copying the archive off a Linux server:

Run in terminalpowershell
scp <your-user>@<your-server-address>:~/docker_volumes/woodpecker/backups/woodpecker-<date>.tar.gz .

For a nightly run, use Task Scheduler to start the WSL command.

Mac (Terminal)#

Use the same scp command from Terminal, or cron for the nightly run.

Performance and resources#

The README says "around 100 MB of RAM (Server) and 30 MB (Agent)" when idle. Steps run in their own containers on top, so your pipelines decide the real load. Measure your own:

  • Live numbers: docker stats woodpecker-server woodpecker-agent while a pipeline runs.
  • Parallel runs: an agent runs one workflow at a time by default. Raise WOODPECKER_MAX_WORKFLOWS on the agent, or add more agents.
  • Database: SQLite is the default and needs no setup. The docs recommend Postgres or MariaDB for larger instances.
  • Image cleanup: the agent does not remove images. The docs show a destructive clean-up command for dangling images and tell you to test it before putting it in cron:
Run in terminalbash
docker image rm $(docker images --filter "dangling=true" -q --no-trunc)

FAQ#

Is Woodpecker CI free to use? Yes, under Apache 2.0. There is no paid edition in the docs we read.

How many pipelines run at once? One workflow per agent by default (WOODPECKER_MAX_WORKFLOWS is 1). Raise it, or add agents, for more parallel runs.

Does it work with GitHub or GitLab? Yes. The docs list GitHub, Gitea, Forgejo, GitLab and Bitbucket as forges. Only one forge can be set with variables; extra forges are experimental.

Can I run it without Docker? Yes. The docs list binaries and DEB and RPM packages, and a Local backend that runs steps directly on the host with no isolation. That backend is for trusted setups only.

Can I use Forgejo Actions instead? Yes. Forgejo has its own runner (see our Forgejo guide); Woodpecker is a separate program.

Sources#

Checked against the official docs on 2026-11-05.