Drone is a container-native continuous integration server: every pipeline step runs in its own Docker container, and pipelines are defined in a .drone.yml file in the repository. A Drone installation has two parts, the server, which talks to your Git provider and shows the web interface, and one or more runners, which execute the pipelines. In this tutorial you will run both with Docker Compose on Ubuntu 24.04, connect Drone to GitHub, publish it over HTTPS with Nginx and run a first pipeline.

Prerequisites

To follow this guide you need:

  • A server running Ubuntu 24.04, for example a CubePath VPS, with at least 2 CPU cores, 2 GB of RAM and 20 GB of free disk space. Pipelines run as containers on this server, so size it for your builds.
  • A non-root user with sudo privileges.
  • A domain name with a DNS A record pointing to the server. This guide uses drone.your_domain.
  • A GitHub account with admin access to the repositories you want to build.
  • Ports 22, 80 and 443 reachable from the Internet.

Step 1 - Installing Docker

Drone and its pipelines run on Docker. Install Docker Engine and the Compose plugin from Docker's official repository. Add its signing key:

sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

Add the repository:

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Install the packages:

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Verify that Docker and Compose work:

sudo docker run --rm hello-world
docker compose version
Hello from Docker!
...
Docker Compose version v2.39.4

Step 2 - Creating a GitHub OAuth application

Drone logs users in with GitHub and uses the OAuth token to read repositories and create webhooks. In GitHub, go to Settings > Developer settings > OAuth Apps and click New OAuth App. Fill in:

  • Application name: Drone
  • Homepage URL: https://drone.your_domain
  • Authorization callback URL: https://drone.your_domain/login

Click Register application, then Generate a new client secret. Copy the Client ID and the client secret; GitHub only shows the secret once.

Step 3 - Generating the shared RPC secret

The server and the runner authenticate each other with a shared secret. Generate a random one:

openssl rand -hex 16
bea26a2221fd8090ea38720fc445eca6

Keep it for the next step.

Step 4 - Writing the Docker Compose file

Create a directory for Drone:

sudo mkdir -p /opt/drone
cd /opt/drone

Store the secrets in an environment file that only root can read:

sudo nano /opt/drone/.env

Replace the values with your own. DRONE_ADMIN_USER is your GitHub username, which becomes a Drone administrator on first login:

DRONE_GITHUB_CLIENT_ID=your_client_id
DRONE_GITHUB_CLIENT_SECRET=your_client_secret
DRONE_RPC_SECRET=your_rpc_secret
DRONE_SERVER_HOST=drone.your_domain
DRONE_ADMIN_USER=your_github_username
sudo chmod 600 /opt/drone/.env

Now create the Compose file:

sudo nano /opt/drone/compose.yaml
services:
  drone-server:
    image: drone/drone:2
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - drone-data:/data
    environment:
      DRONE_GITHUB_CLIENT_ID: ${DRONE_GITHUB_CLIENT_ID}
      DRONE_GITHUB_CLIENT_SECRET: ${DRONE_GITHUB_CLIENT_SECRET}
      DRONE_RPC_SECRET: ${DRONE_RPC_SECRET}
      DRONE_SERVER_HOST: ${DRONE_SERVER_HOST}
      DRONE_SERVER_PROTO: https
      DRONE_USER_CREATE: username:${DRONE_ADMIN_USER},admin:true
      DRONE_USER_FILTER: ${DRONE_ADMIN_USER}

  drone-runner:
    image: drone/drone-runner-docker:1
    restart: unless-stopped
    depends_on:
      - drone-server
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      DRONE_RPC_PROTO: http
      DRONE_RPC_HOST: drone-server
      DRONE_RPC_SECRET: ${DRONE_RPC_SECRET}
      DRONE_RUNNER_CAPACITY: 2
      DRONE_RUNNER_NAME: runner-1

volumes:
  drone-data:

Key settings:

  • 127.0.0.1:8080:80: the Drone web interface is only reachable from the server itself; Nginx will publish it over HTTPS.
  • drone-data:/data: Drone stores its SQLite database here, including users, repositories, secrets and build logs.
  • DRONE_SERVER_PROTO: https: Drone generates links and webhook URLs with https://, matching the Nginx setup.
  • DRONE_USER_FILTER: only the listed GitHub users (or organizations, comma-separated) can log in. Without it, anyone with a GitHub account could register on your instance.
  • DRONE_RPC_HOST: drone-server: the runner reaches the server over the internal Compose network by service name.
  • /var/run/docker.sock: the runner starts pipeline containers through the host's Docker daemon.
  • DRONE_RUNNER_CAPACITY: how many pipelines this runner executes at the same time.

Start the stack:

sudo docker compose up -d

Check that the runner connected to the server:

sudo docker compose logs drone-runner
drone-runner-1  | time="2026-09-24T12:30:11Z" level=info msg="starting the server" addr=":3000"
drone-runner-1  | time="2026-09-24T12:30:11Z" level=info msg="successfully pinged the remote server"
drone-runner-1  | time="2026-09-24T12:30:11Z" level=info msg="polling the remote server" arch=amd64 capacity=2 endpoint="http://drone-server" kind=pipeline os=linux type=docker

Step 5 - Publishing Drone over HTTPS with Nginx

GitHub has to reach Drone to deliver webhooks and complete the OAuth login, so publish it on your domain. Install Nginx and Certbot:

sudo apt install -y nginx certbot python3-certbot-nginx

Create a server block:

sudo nano /etc/nginx/sites-available/drone
server {
    listen 80;
    listen [::]:80;
    server_name drone.your_domain;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_read_timeout 300s;
    }
}

proxy_buffering off lets build logs stream live to the browser. Enable the site, test the syntax and reload:

sudo ln -s /etc/nginx/sites-available/drone /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

If UFW is active, allow web traffic:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'

Request a Let's Encrypt certificate. Certbot adds the HTTPS configuration and the redirect from HTTP:

sudo certbot --nginx -d drone.your_domain
Successfully deployed certificate for drone.your_domain to /etc/nginx/sites-enabled/drone
Congratulations! You have successfully enabled HTTPS on https://drone.your_domain

Step 6 - Logging in and activating a repository

Open https://drone.your_domain and click Continue. GitHub asks you to authorize the Drone application; after you accept, Drone syncs your repositories and lists them on the dashboard.

Select a repository and click Activate Repository. Drone creates a webhook in the GitHub repository, visible under the repository's Settings > Webhooks with a green check after the first delivery. In the repository's settings in Drone you can also mark it as Trusted, which is only needed for pipelines that mount host volumes or run privileged containers; leave it off unless you need it.

Step 7 - Creating your first pipeline

In the root of the activated repository, create .drone.yml. This example tests a Node.js project, but any image works:

kind: pipeline
type: docker
name: default

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

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

  - name: build
    image: node:22
    commands:
      - npm run build
    when:
      branch:
        - main
      event:
        - push

trigger:
  event:
    - push
    - pull_request

Steps run in order and share the cloned workspace, so node_modules installed in the first step is available to the next ones. The when block runs build only for pushes to main, and trigger limits the pipeline to push and pull request events.

Commit and push the file:

git add .drone.yml
git commit -m "Add Drone pipeline"
git push origin main

The build appears in the Drone dashboard within a few seconds. Click it to see each step's live log. A passing build shows every step in green and the commit gets a green check in GitHub.

Step 8 - Using secrets in a pipeline

Never put credentials in .drone.yml. Add them in Drone under the repository's Settings > Secrets instead, for example a secret named npm_token. Reference it in a step with from_secret:

  - name: publish
    image: node:22
    environment:
      NPM_TOKEN:
        from_secret: npm_token
    commands:
      - echo "//registry.npmjs.org/:_authToken=$${NPM_TOKEN}" > .npmrc
      - npm publish
    when:
      event:
        - tag

The double dollar sign $$ stops Drone from substituting the variable itself, so the shell inside the container expands it. By default, secrets are not exposed to pull requests coming from forks.

Step 9 - Backing up Drone

All of Drone's state is in the drone-data volume. The SQLite database is a single file, so you can copy it safely while the server is briefly stopped:

cd /opt/drone
sudo docker compose stop drone-server
sudo docker run --rm -v drone_drone-data:/data -v /var/backups:/backup alpine:3.20 \
  tar -czf /backup/drone-data-$(date +%F).tar.gz -C /data .
sudo docker compose start drone-server
ls -lh /var/backups/drone-data-*.tar.gz

The volume name is prefixed with the Compose project name, which is the directory name (drone). Check it with sudo docker volume ls if the command reports a missing volume. Keep a copy of /opt/drone/.env together with the archive.

Troubleshooting

  • Login loops back to GitHub or fails with redirect_uri mismatch: the callback URL in the OAuth app must be exactly https://drone.your_domain/login and DRONE_SERVER_HOST must match the domain without https://.
  • "Unauthorized" or "user registration is disabled" after login: your GitHub username is not in DRONE_USER_FILTER. Add it to .env and run sudo docker compose up -d to recreate the container.
  • Builds stay in pending: the runner is not connected. Check sudo docker compose logs drone-runner; an authentication error means DRONE_RPC_SECRET differs between the server and the runner.
  • Pushes do not start builds: open the webhook in GitHub under Settings > Webhooks and check Recent Deliveries. A failed delivery usually means the domain does not resolve to the server or the certificate is invalid.
  • Changes to .env have no effect: Compose only reads it when containers are created. Run sudo docker compose up -d --force-recreate.

Conclusion

Drone now runs on Ubuntu 24.04 behind Nginx with HTTPS, logs in through GitHub, and runs container-based pipelines from .drone.yml on a local Docker runner. As next steps, add more runners on other servers by starting the runner container with the same DRONE_RPC_SECRET and DRONE_RPC_HOST pointing to your domain with DRONE_RPC_PROTO=https, use Drone plugins such as plugins/docker to build and push images, and schedule the backup from Step 9 with cron.