Drone is a container-native CI/CD system: every pipeline step runs in its own Docker container, defined in a simple .drone.yml in your repository. It connects to GitHub, GitLab, Gitea, Gogs and Bitbucket, and runs comfortably on a small VPS. It’s a lightweight alternative to Jenkins or GitLab CI for self-hosters.

Licensing note: Drone is now owned by Harness. The open-source edition has limitations, and the community fork Woodpecker CI continues the original Apache-licensed codebase. For new setups, compare both. See Woodpecker CI.

Architecture

  • Drone server: web UI, API, receives webhooks from your Git host, schedules builds.
  • Runners: execute pipelines. The Docker runner runs each step in a container on its host.

Both can run on the same VPS, or put runners on separate build servers.

Requirements

  • A VPS with Docker. 2 vCPU and 2–4 GB RAM is enough for small teams (builds need more). See install Docker.
  • A domain (for example ci.example.com) with HTTPS.
  • A Git host to connect: Gitea is a natural self-hosted pair. See Gitea.

1. Create an OAuth application

In Gitea: Settings → Applications → Create OAuth2 Application, with the redirect URI https://ci.example.com/login. Note the client ID and secret. (GitHub: Developer settings → OAuth Apps.)

2. Run server and runner

# /opt/drone/compose.yaml
services:
  drone:
    image: drone/drone:2
    restart: unless-stopped
    environment:
      DRONE_GITEA_SERVER: https://git.example.com
      DRONE_GITEA_CLIENT_ID: <client-id>
      DRONE_GITEA_CLIENT_SECRET: <client-secret>
      DRONE_RPC_SECRET: <openssl rand -hex 16>
      DRONE_SERVER_HOST: ci.example.com
      DRONE_SERVER_PROTO: https
      DRONE_USER_CREATE: username:youradmin,admin:true
    volumes:
      - ./data:/data
    ports:
      - "127.0.0.1:8080:80"

  runner:
    image: drone/drone-runner-docker:1
    restart: unless-stopped
    environment:
      DRONE_RPC_PROTO: http
      DRONE_RPC_HOST: drone
      DRONE_RPC_SECRET: <same secret>
      DRONE_RUNNER_CAPACITY: 2
      DRONE_RUNNER_NAME: runner-1
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
cd /opt/drone && docker compose up -d

Put Nginx with HTTPS in front of port 8080. See Nginx reverse proxy and Let’s Encrypt.

The runner mounts the Docker socket, so pipelines can effectively control the host. Run it on a dedicated build VPS, and only for repositories you trust.

3. Activate a repository and add a pipeline

Log in to https://ci.example.com, activate the repository, and commit .drone.yml:

kind: pipeline
type: docker
name: default

steps:
  - name: test
    image: node:22
    commands:
      - npm ci
      - npm test

  - name: build-and-push
    image: plugins/docker
    settings:
      registry: registry.example.com
      repo: registry.example.com/myapp
      tags: [ "${DRONE_COMMIT_SHA:0:8}" ]
      username: { from_secret: registry_user }
      password: { from_secret: registry_password }
    when:
      branch: [ main ]

  - name: deploy
    image: appleboy/drone-ssh
    settings:
      host: { from_secret: vps_host }
      username: deploy
      key: { from_secret: vps_ssh_key }
      script:
        - cd /opt/app && docker compose pull && docker compose up -d
    when:
      branch: [ main ]

Add secrets in the repository settings in the Drone UI (or with the drone CLI). See Docker private registry.

Tips

  • Use when: conditions for branches, events (push, tag, pull_request) and paths.
  • Cache dependencies with volume or cache plugins to speed up builds.
  • Keep runner hosts updated, and clean old images regularly (docker system prune via a cron job). See Docker disk cleanup.

Quick reference

Item Value
Server image drone/drone:2
Runner image drone/drone-runner-docker:1
Shared secret DRONE_RPC_SECRET
Pipeline file .drone.yml
Secrets from_secret:
Open-source fork Woodpecker CI

Related: Gitea Actions and CI/CD pipeline explained.

  • CI/CD
  • Docker
  • Self-Hosting