Developer Tools Azure

Gitea + Woodpecker CI DevOps Stack on Ubuntu 24.04 on Azure User Guide

| Product: Gitea + Woodpecker CI DevOps Stack on Ubuntu 24.04 LTS on Azure

Overview

This image is a self-contained DevOps stack. It bundles Gitea (a lightweight self-hosted Git service, MIT) as the Git forge, and Woodpecker CI (a simple, container-based continuous integration engine, Apache-2.0) as the pipeline runner — server plus agent. Woodpecker CI cannot run standalone: it has no user database of its own and authenticates users through a Git forge over OAuth. This image supplies that forge on the same VM and pre-wires the OAuth handshake between the two, so a complete source-control and CI/CD pair is running within minutes of launch, with no external service and no manual configuration.

Every application port binds to the loopback interface and is published by nginx: the Woodpecker UI on port 80, and the bundled Gitea on port 3000. Pipeline steps run in isolated Docker containers, so real builds execute on the one VM.

Services, loopback bindings and versions

What is included:

  • Gitea 1.27.1 single Go binary (/usr/local/bin/gitea), SQLite database, forge + OAuth provider
  • Woodpecker CI 3.17.0 server + agent + CLI (/usr/local/bin/woodpecker-*), Docker pipeline backend
  • nginx reverse proxy: port 80 → Woodpecker UI, port 3000 → bundled Gitea
  • Four managed services: gitea.service, woodpecker-server.service, woodpecker-agent.service, nginx.service
  • woodpecker-firstboot.service — rotates every secret, recreates the OAuth app against the VM address, and writes the per-VM administrator login on first boot
  • All data under /var/lib/woodpecker (Gitea repositories + both SQLite databases)
  • Gitea open registration disabled and the installer locked; Woodpecker restricted to Gitea users
  • Ubuntu 24.04 LTS base, fully patched; 24/7 cloudimg support

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet + subnet. Recommended VM size: Standard_B2s (2 vCPU / 4 GB) — enough for Gitea, the Woodpecker server and agent, and container-based pipelines for a small team.

Step 1: Deploy from the Azure Portal

Search the Marketplace for Gitea + Woodpecker CI DevOps Stack, choose the plan, and create the VM. In the networking step attach a Network Security Group that allows inbound TCP 22 (SSH) from your management network, and TCP 80 (Woodpecker UI) and TCP 3000 (Gitea) from the client networks that need them. Put a TLS-terminating reverse proxy or Azure Application Gateway in front of ports 80/3000 for production.

Step 2: Deploy from the Azure CLI

RG="devops-prod"; LOCATION="eastus"; VM_NAME="gitea-woodpecker-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/gitea-woodpecker-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name devops-vnet --address-prefix 10.104.0.0/16 --subnet-name devops-subnet --subnet-prefix 10.104.1.0/24
az network nsg create -g "$RG" --name devops-nsg
az network nsg rule create -g "$RG" --nsg-name devops-nsg --name allow-ssh --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name devops-nsg --name allow-woodpecker --priority 110 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 80 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name devops-nsg --name allow-gitea --priority 120 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 3000 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name devops-vnet --subnet devops-subnet --nsg devops-nsg --public-ip-sku Standard

Step 3: Connect via SSH

ssh azureuser@<vm-ip>

Step 4: Retrieve the per-VM administrator login

On the first boot, woodpecker-firstboot.service generates a fresh Gitea administrator password (and fresh server secrets, agent secret and OAuth client secret) unique to your VM, and writes the login to a root-only file. No shared or default credentials ship in the image.

sudo cat /root/woodpecker-credentials.txt

Per-VM administrator credentials, rotated on first boot

The file records the Woodpecker URL, the Gitea URL, and the gitadmin username and password. Store them somewhere safe.

Step 5: Verify the stack and the OAuth wiring

Confirm all four services are active:

systemctl is-active gitea.service woodpecker-server.service woodpecker-agent.service nginx.service

Check the Woodpecker health endpoint (open) and the bundled Gitea health endpoint through nginx:

curl -s -o /dev/null -w 'woodpecker healthz: %{http_code}\n' http://127.0.0.1/healthz
curl -s http://127.0.0.1:3000/api/healthz | head -c 40; echo

Prove the Gitea administrator login round-trips (200 with credentials, 401 without):

curl -s -o /dev/null -w 'with creds:    %{http_code}\n' -u "gitadmin:<GITEA_ADMIN_PASSWORD>" http://127.0.0.1:3000/api/v1/user
curl -s -o /dev/null -w 'without creds: %{http_code}\n' http://127.0.0.1:3000/api/v1/user

Confirm Woodpecker is pointed at the bundled Gitea and its /authorize redirects into the Gitea OAuth flow with the wired client id:

grep -E '^WOODPECKER_(HOST|GITEA_URL|GITEA_CLIENT)=' /etc/woodpecker/server.env
curl -s -o /dev/null -w 'oauth redirect: %{redirect_url}\n' -m 10 http://127.0.0.1:8000/authorize | sed 's/&state=.*//'

Pre-wired Gitea OAuth handshake and the registered Woodpecker agent

Step 6: Sign in to Woodpecker through Gitea

Browse to http://<vm-ip>/ — the Woodpecker sign-in page offers a single button to sign in through the bundled Gitea forge.

Woodpecker sign-in via the bundled Gitea

Click it, sign in with the gitadmin username and password from Step 4, and click Authorize on the Gitea consent screen. You are returned to Woodpecker, signed in — the bundled Gitea administrator is also the Woodpecker administrator.

Signed in to Woodpecker via Gitea OAuth

Step 7: Run your first pipeline

Create a repository in the bundled Gitea (http://<vm-ip>:3000/), add a .woodpecker.yml file to it, then in Woodpecker click Add repository, enable the repo, and push a commit. Gitea's webhook triggers Woodpecker, and the agent runs each step in a Docker container. A minimal pipeline:

steps:
  - name: smoke
    image: alpine:3.20
    commands:
      - echo "hello from cloudimg"
      - uname -sm

The run appears in Woodpecker with per-step logs and a green result when the container exits 0:

A Woodpecker pipeline run completed via the Docker agent

You can also run a pipeline locally against the Docker backend without pushing, using the bundled CLI — handy for testing a .woodpecker.yml:

cat > /tmp/.woodpecker.yml <<'YAML'
steps:
  - name: smoke
    image: alpine:3.20
    commands:
      - echo "hello from cloudimg"
YAML
cd /tmp && sudo WOODPECKER_BACKEND=docker woodpecker-cli exec --backend-engine docker .woodpecker.yml

A real Woodpecker pipeline executing in a Docker container

Step 8: Point Woodpecker at an external forge (optional)

To use an external GitHub, Gitea, Forgejo or GitLab forge instead of the bundled one, register an OAuth application on that forge, then edit /etc/woodpecker/server.env — set WOODPECKER_GITEA_URL (or the GitHub/GitLab equivalents), WOODPECKER_GITEA_CLIENT and WOODPECKER_GITEA_SECRET — and restart:

sudo systemctl restart woodpecker-server.service

Step 9: Production hardening

  • TLS. Terminate HTTPS in front of ports 80 and 3000 with Azure Application Gateway, a load balancer, or your own reverse proxy; or install a certificate with certbot on a public DNS name and front nginx.
  • Backups. Back up /var/lib/woodpecker (Gitea repositories and both SQLite databases).
  • Scaling. Increase WOODPECKER_MAX_WORKFLOWS in /etc/woodpecker/agent.env, or add more agents, to run more pipeline steps in parallel.

Operations and support

Manage the stack with systemctl (gitea, woodpecker-server, woodpecker-agent, nginx). The Woodpecker health endpoint http://<vm-ip>/healthz is open for monitoring. cloudimg provides 24/7 technical support by email and chat for deployment, pointing Woodpecker at an external forge, pipeline authoring, agent scaling, TLS termination and upgrades.