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.
NoteDrone is now developed by Harness, and its successor is Harness Open Source. Drone 2.x is still distributed as the
drone/droneimage and works as described here, but check the project's status before adopting it for a new long-term setup.
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
sudoprivileges. - 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 withhttps://, 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.
WarningAccess to the Docker socket is equivalent to root on the host. Anyone who can change a
.drone.ymlin an activated repository can run containers on this server, so only activate repositories you trust.
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_urimismatch: the callback URL in the OAuth app must be exactlyhttps://drone.your_domain/loginandDRONE_SERVER_HOSTmust match the domain withouthttps://. - "Unauthorized" or "user registration is disabled" after login: your GitHub username is not in
DRONE_USER_FILTER. Add it to.envand runsudo docker compose up -dto recreate the container. - Builds stay in
pending: the runner is not connected. Checksudo docker compose logs drone-runner; an authentication error meansDRONE_RPC_SECRETdiffers 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
.envhave no effect: Compose only reads it when containers are created. Runsudo 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.