--- title: "How to Install Drone CI with Docker Compose on Ubuntu 24.04" description: "Run a Drone CI server and Docker runner with Docker Compose on Ubuntu 24.04, connect it to GitHub over HTTPS with Nginx and run your first .drone.yml pipeline." category: "CI/CD DevOps" language: en source: https://cubepath.com/docs/ci-cd-devops/drone-ci-installation-and-configuration updated: 2026-09-25 --- # How to Install Drone CI with Docker Compose on Ubuntu 24.04 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. > Note: Drone is now developed by Harness, and its successor is Harness Open Source. Drone 2.x is still distributed as the `drone/drone` image 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 `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: ```bash 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: ```bash 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: ```bash 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: ```bash sudo docker run --rm hello-world docker compose version ``` ```output 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: ```bash openssl rand -hex 16 ``` ```output bea26a2221fd8090ea38720fc445eca6 ``` Keep it for the next step. ## Step 4 - Writing the Docker Compose file Create a directory for Drone: ```bash sudo mkdir -p /opt/drone cd /opt/drone ``` Store the secrets in an environment file that only root can read: ```bash 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: ```ini 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 ``` ```bash sudo chmod 600 /opt/drone/.env ``` Now create the Compose file: ```bash sudo nano /opt/drone/compose.yaml ``` ```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. > Warning: Access to the Docker socket is equivalent to root on the host. Anyone who can change a `.drone.yml` in an activated repository can run containers on this server, so only activate repositories you trust. Start the stack: ```bash sudo docker compose up -d ``` Check that the runner connected to the server: ```bash sudo docker compose logs drone-runner ``` ```output 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: ```bash sudo apt install -y nginx certbot python3-certbot-nginx ``` Create a server block: ```bash sudo nano /etc/nginx/sites-available/drone ``` ```nginx 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: ```bash 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: ```bash 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: ```bash sudo certbot --nginx -d drone.your_domain ``` ```output 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: ```yaml 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: ```bash 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`: ```yaml - 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: ```bash 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.