Test and Deploy Django With Woodpecker CI
Set up Woodpecker CI, a lightweight self-hosted CI engine, for Django: write .woodpecker.yaml pipelines with a PostgreSQL service, run tests and linting, build and push images with the buildx plugin, deploy over SSH on main, and manage secrets.
Problem statement
You host your own Git server (Gitea, Forgejo, or GitLab) or want CI that runs entirely on your hardware with minimal resources. Woodpecker CI is a community fork of Drone: a small server plus agents that run each pipeline step in a container. A Django pipeline needs a PostgreSQL service for tests, an image build, a deploy step limited to the main branch, and secrets stored in Woodpecker rather than in the repository.
- Run the Woodpecker server and at least one agent, connected to your Git forge with OAuth.
- Add
.woodpecker.yamlwith aservicesentry for PostgreSQL and ateststep using your Python image. - Build and push the image with the
woodpeckerci/plugin-docker-buildxplugin, tagged with${CI_COMMIT_SHA}. - Add a deploy step with
when: branch: main, event: pushthat runs your deploy script over SSH. - Store registry and SSH credentials as Woodpecker secrets, exposed with
from_secret.
Step-by-step solution
1. Install Woodpecker
Run the server and agent with Docker Compose, pointing the server at your forge (Gitea, Forgejo, GitHub, GitLab, or Bitbucket) with OAuth credentials. Agents connect to the server with a shared secret and run steps using the Docker backend (or Kubernetes). Activate the repository in the Woodpecker UI.
2. Write the pipeline
# .woodpecker.yaml
when:
- event: [push, pull_request]
services:
- name: db
image: postgres:17
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: shop
steps:
- name: test
image: python:3.12-slim
environment:
DATABASE_URL: postgres://postgres:postgres@db:5432/shop
DJANGO_SETTINGS_MODULE: config.settings.test
commands:
- pip install -r requirements.txt
- for i in $(seq 1 30); do python -c "import os, psycopg; psycopg.connect(os.environ['DATABASE_URL']).close()" && break; sleep 1; done
- python manage.py check --deploy --fail-level WARNING
- python manage.py test --noinput
Services are reachable by name (db). The loop retries a real connection for up to 30 seconds, because the service container may still be starting when the step begins.
3. Build and push the image
- name: build
image: woodpeckerci/plugin-docker-buildx
settings:
registry: git.example.com
repo: git.example.com/acme/shop
tags: [ "${CI_COMMIT_SHA}" ]
cache_from: git.example.com/acme/shop:buildcache
username: { from_secret: registry_user }
password: { from_secret: registry_password }
when:
- branch: main
event: push
Gitea and Forgejo include a container registry, so images can live next to the code.
4. Deploy on main
- name: deploy
image: alpine:3.20
environment:
SSH_KEY: { from_secret: deploy_ssh_key }
KNOWN_HOSTS: { from_secret: deploy_known_hosts }
commands:
- apk add --no-cache openssh-client
- mkdir -p ~/.ssh && echo "$SSH_KEY" > ~/.ssh/id_ed25519 && chmod 600 ~/.ssh/id_ed25519
- echo "$KNOWN_HOSTS" > ~/.ssh/known_hosts
- ssh deploy@app.example.com "/srv/shop/bin/deploy ${CI_COMMIT_SHA}"
when:
- branch: main
event: push
The server-side deploy script pulls the image, runs migrations, and restarts services. Storing known_hosts as a secret keeps host key verification on.
5. Secrets and safety
Create secrets in the repository settings and restrict them to specific events (push, not pull request) and images where possible. Woodpecker does not expose secrets to pull requests from forks by default. Use separate SSH keys per environment, limited to running the deploy command on the server through authorized_keys options.
Explanation
Woodpecker runs each step as a container on an agent, sharing a workspace volume with the cloned repository. Services run alongside steps on the same network for the duration of the pipeline. Conditions (when) select which steps run for each event and branch, and secrets are injected only into the steps that reference them. The small footprint suits self-hosted setups alongside a Gitea or Forgejo server.
Edge cases / notes
- Caching pip. Steps start from fresh containers; mount a cache volume (if your agent allows it) or build a test image with dependencies preinstalled.
- Multiple workflows. A
.woodpecker/directory with several YAML files splits test, build, and deploy into separate workflows with dependencies. - Matrix builds.
matrixcan test several Python or Django versions in parallel.
Next steps
- Using Gitea? Compare with Test and Deploy Django With Gitea Actions.
- See a hosted equivalent in Deploy Django With GitHub Actions CI/CD.
- Review gates with the Django CI/CD Pipeline Checklist.
FAQ
Is Woodpecker the same as Drone?
It is a community fork of Drone with its own development since 2019.
How do I provide PostgreSQL for tests?
Add it under services; steps reach it by its service name.
How do I deploy only from main?
Add when: branch: main, event: push to the build and deploy steps.
Are secrets safe in pull requests?
By default, secrets are not passed to pull requests from forks; restrict them to push events anyway.