Skip to main contentSkip to navigation
[email protected]
Client AreaSupport
Hosting Mammoth
HostingMammothYour Data, Our Responsibility
Home
Solutions
Hosting Services
Store
Pricing
About
Blog
API
Contact

Stay Ahead of the Curve

Get the latest insights on cybersecurity, AI innovations, and enterprise data solutions delivered to your inbox.

Hosting Mammoth
HostingMammothEnterprise Solutions

Enterprise-grade data solutions. Hosting, recovery, cybersecurity, and AI-powered services for businesses worldwide.

[email protected]
Sun - Fri, 9:00am - 5:00pm

Services

  • Cloud Hosting
  • Data Recovery
  • Cybersecurity
  • Legal Support
  • MSP Services
  • Web Development
  • AI Services
  • Free Server Migration

Hosting

  • VPS Hosting (NVMe SSD)
  • VDS Hosting (NVMe)
  • Storage VPS (High SSD)
  • GPU Servers
  • Managed Services
  • Cloud Firewall
  • Load Balancer
  • One-Click Apps
  • n8n Hosting
  • Object Storage
  • FAQ

Company

  • Store
  • Pricing
  • About Us
  • Locations
  • Blog
  • Testimonials
  • Contact
  • Affiliate Program
  • White-Label
  • Terms of Service
  • Privacy Policy
  • Browser Cookies
  • SLA

Support

  • Client Area
  • Submit Ticket
  • Knowledge Base
  • Server Status
  • API Documentation

© 2026 Hosting Mammoth. All rights reserved.

Knowledge Base
Getting StartedAccount ManagementVPS HostingGPU ServersStorage VPSCloud FirewallLoad BalancerServer ManagementBilling & PaymentsSupport & TicketsAffiliate ProgramReseller ProgramMarketplace & Appsn8n HostingManaged ServicesServer MigrationAPI & DevelopersSecurityTroubleshootingGlossaryInstall Guides
  1. Home
  2. /
  3. Support
  4. /
  5. Install Guides
  6. /
  7. How To Install Drone Ci Ubuntu
GUIDEInstall Guides

How to Install Drone CI on Ubuntu 24.04 VPS: Container-Native Continuous Delivery

27 min read

How to Install Drone CI on Ubuntu 24.04 VPS: Container-Native Continuous Delivery

Drone CI is a container-native continuous integration and delivery platform that turns every pipeline step into a Docker container. Every build runs in a clean, reproducible environment, which makes "works on my machine" problems almost impossible. This guide walks through deploying a production-grade Drone CI server on Ubuntu 24.04, from Docker install to Nginx TLS termination, OAuth with GitHub or Gitea, .drone.yml pipelines, secrets, image promotion, and scaling runners.

Note on branding: Drone was acquired by Harness in 2020 and the commercial product was rebranded. The open-source drone/drone and drone-runner-docker images on Docker Hub are still published under the Apache 2.0 license and remain fully maintained. Self-hosting Drone is free, unlimited, and the workflow has not changed.

Table of Contents

  • What is Drone CI?
  • Why Self-Host Your CI/CD Pipeline?
  • Prerequisites
  • Step 1: Update System Packages
  • Step 2: Install Docker and Docker Compose
  • Step 3: Register an OAuth Application
  • Step 4: Create the Docker Compose Stack
  • Step 5: Configure Nginx with TLS
  • Step 6: Launch Drone and Activate a Repository
  • Step 7: Write Your First .drone.yml
  • Step 8: Triggers, Conditions, and Image Promotion
  • Step 9: Secrets Management
  • Step 10: Scale Runners Horizontally
  • Performance and Hardening
  • Troubleshooting
  • FAQ
  • Next Steps
  • What is Drone CI?

    Drone CI is an open-source, container-native continuous integration system originally created by Brad Rydzewski. It consists of two core components: a server that handles webhooks, UI, API, and database state, and one or more runners that execute pipeline steps in Docker containers. The server speaks to your Git provider (GitHub, Gitea, GitLab, Bitbucket) over OAuth, receives webhooks on push or pull request events, and dispatches work to runners over a gRPC-based RPC protocol.

    Every step in a Drone pipeline runs inside a container you specify. Want Node 20? Pull node:20-alpine. Need Go 1.22? Pull golang:1.22. Need both in the same pipeline? List them as separate steps and they will run sequentially in their own clean environments. This container-native design eliminates host-level toolchain management: the runner host only needs Docker, nothing else.

    Common workflows built with Drone include pull-request validation (lint, unit tests, integration tests on every PR), container image builds published to a registry with semver tags, deploy pipelines that SSH into servers or apply Kubernetes manifests, release automation that cuts a GitHub release and uploads artifacts when a tag is pushed, and scheduled jobs for nightly test suites or cache warming. The .drone.yml file lives at the repository root and is version-controlled alongside application code, so pipeline changes go through the same review process as any other code change.

    Drone's plugin ecosystem covers hundreds of common tasks: plugins/docker for image builds, plugins/s3 for artifact uploads, plugins/slack for notifications, plugins/ssh for remote execution, appleboy/drone-scp for file transfers, and many more. Every plugin is itself a container image, so running one is the same as running any other step.

    Why Self-Host Your CI/CD Pipeline?

    Hosted CI services (GitHub Actions, CircleCI, Travis, Buildkite cloud) are convenient, but self-hosting Drone on your own VPS offers distinct advantages:

    • Predictable flat-rate cost -- Hosted CI is priced per build minute or per concurrent job. Heavy pipelines can cost hundreds of dollars per month. A single VPS running Drone costs the same whether you run 10 builds or 10,000.
    • No minute limits or queue throttling -- When GitHub Actions free minutes run out, builds stop. Your own runner queues work until the CPU is saturated and then keeps going.
    • Source code stays on your infrastructure -- For proprietary codebases, regulated industries, or clients with strict data-residency requirements, keeping source, build artifacts, and secrets on a known server (not a shared cloud) simplifies compliance.
    • Unlimited concurrent jobs -- Add more runners for more parallelism. No plan upgrades, no rate limits.
    • Custom hardware -- Need a runner with a GPU, a specific CPU instruction set, or local NVMe? Provision a VPS that matches and point a runner at it.
    • Full control of the supply chain -- You pin exact image versions, mirror registries internally, and decide what reaches the build environment.
    • Works with self-hosted Git -- If you already run Gitea, Drone integrates natively and your entire dev loop lives on your own servers.

    Cost Comparison: Self-Hosted vs. Hosted CI

    ScenarioGitHub Actions (paid)CircleCI PerformanceSelf-Hosted Drone on VPS
    Monthly cost (moderate use)~$40-120/mo (per-minute)$30/mo + usageEUR 19.99/mo (flat)
    Concurrent jobs20 (paid tier)30 (Performance)Unlimited (runner capacity)
    Build minutes cap3,000-50,00080,000 creditsNone
    Source leaves your server?YesYesNo
    Custom runner hardwareLimitedLimitedYes (any VPS size)
    Typical cost at 500 builds/month~$60/mo~$50/moEUR 19.99/mo
    Teams running more than a handful of builds per day usually recoup the VPS cost within the first month.

    Prerequisites

    Before you begin, make sure you have:

    • A VPS running Ubuntu 24.04 LTS with root or sudo access
    • SSH access to your server
    • A domain name pointing to your VPS (e.g. drone.yourdomain.com) -- required for OAuth callback URLs and TLS
    • A GitHub, Gitea, or GitLab account with admin rights on at least one repository you want to build
    • At least 4 GB of RAM (8-12 GB recommended for parallel builds)
    • 20 GB+ of free disk space for Docker images and build caches
    Recommended Plan: CloudCore Professional
    >
    For a Drone server plus one or two concurrent runners, we recommend the CloudCore Professional plan:
    >
    - 6 vCPU cores
    - 12 GB RAM
    - 100 GB NVMe SSD
    - Unmetered bandwidth
    - EUR 19.99/month
    >
    This gives comfortable headroom for parallel Node, Go, or Docker-in-Docker builds while keeping the Drone server responsive.

    Connect to your server via SSH:

    bash
    ssh root@your-server-ip

    Also make sure your domain's A record points to the server's IP. You can verify from the server:

    bash
    dig +short drone.yourdomain.com

    It should return your VPS IP.

    Step 1: Update System Packages

    Start by updating the package index and upgrading installed packages:

    bash
    sudo apt update && sudo apt upgrade -y

    Install a few utilities you will need later:

    bash
    sudo apt install -y curl ca-certificates gnupg lsb-release ufw

    If the kernel was updated, reboot before continuing:

    bash
    sudo reboot

    Step 2: Install Docker and Docker Compose

    Drone runs entirely in Docker. If you have not already installed Docker, follow the abridged steps below, or see the dedicated guide: How to Install Docker on Ubuntu.

    Add Docker's official GPG key and repository:

    bash
    sudo install -m 0755 -d /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
    sudo chmod a+r /etc/apt/keyrings/docker.gpg

    echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] 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 Docker Engine and the Compose plugin:

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

    Verify both:

    bash
    docker --version
    docker compose version

    Expected output:

    text
    Docker version 27.0.3, build 7d4bcd8
    Docker Compose version v2.29.1

    Allow your non-root user to run Docker (optional but recommended):

    bash
    sudo usermod -aG docker $USER
    newgrp docker

    Open the firewall for SSH, HTTP, and HTTPS:

    bash
    sudo ufw allow OpenSSH
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw --force enable

    Step 3: Register an OAuth Application

    Drone authenticates users and pulls repositories through OAuth. Register an application with your Git provider before bringing up the stack.

    Option A: GitHub OAuth App

  • Go to GitHub -> Settings -> Developer settings -> OAuth Apps -> New OAuth App.
  • Fill in the form:
  • - Application name: Drone CI - Homepage URL: https://drone.yourdomain.com - Authorization callback URL: https://drone.yourdomain.com/login
  • Click Register application.
  • Copy the Client ID. Click Generate a new client secret and copy the Client Secret. You will paste these into Docker Compose shortly.
  • Option B: Gitea OAuth Application

    If you have Gitea running on the same network:

  • Log in to Gitea as an admin.
  • Go to Settings -> Applications -> Manage OAuth2 Applications -> Create new.
  • Fill in:
  • - Application name: Drone CI - Redirect URI: https://drone.yourdomain.com/login
  • Click Create Application and copy the Client ID and Client Secret.
  • Generate an RPC Shared Secret

    The Drone server and its runners authenticate each other with a shared secret. Generate a strong one now:

    bash
    openssl rand -hex 16

    Save the output -- you will use it for DRONE_RPC_SECRET.

    Step 4: Create the Docker Compose Stack

    Create a working directory and the Compose file.

    bash
    sudo mkdir -p /opt/drone
    sudo chown $USER:$USER /opt/drone
    cd /opt/drone

    Create an .env file for your secrets:

    bash
    cat > /opt/drone/.env <<'EOF'
    DRONE_SERVER_HOST=drone.yourdomain.com
    DRONE_SERVER_PROTO=https

    --- GitHub OAuth (comment out if using Gitea) ---

    DRONE_GITHUB_CLIENT_ID=your_github_client_id DRONE_GITHUB_CLIENT_SECRET=your_github_client_secret

    --- Gitea OAuth (uncomment to use Gitea instead of GitHub) ---

    DRONE_GITEA_SERVER=https://git.yourdomain.com

    DRONE_GITEA_CLIENT_ID=your_gitea_client_id

    DRONE_GITEA_CLIENT_SECRET=your_gitea_client_secret

    DRONE_RPC_SECRET=paste_the_openssl_rand_hex_output_here DRONE_USER_CREATE=username:your-github-or-gitea-username,admin:true EOF chmod 600 /opt/drone/.env

    Replace the placeholder values with real credentials. The DRONE_USER_CREATE line pre-promotes your account to admin on first login.

    Create the docker-compose.yml:

    bash
    cat > /opt/drone/docker-compose.yml <<'EOF'
    version: "3.8"

    services: drone-server: image: drone/drone:2 container_name: drone-server restart: unless-stopped ports: - "127.0.0.1:8080:80" volumes: - drone-data:/data environment: - DRONE_SERVER_HOST=${DRONE_SERVER_HOST} - DRONE_SERVER_PROTO=${DRONE_SERVER_PROTO} - DRONE_RPC_SECRET=${DRONE_RPC_SECRET} - DRONE_USER_CREATE=${DRONE_USER_CREATE} # GitHub - DRONE_GITHUB_CLIENT_ID=${DRONE_GITHUB_CLIENT_ID} - DRONE_GITHUB_CLIENT_SECRET=${DRONE_GITHUB_CLIENT_SECRET} # Gitea (uncomment if using Gitea) # - DRONE_GITEA_SERVER=${DRONE_GITEA_SERVER} # - DRONE_GITEA_CLIENT_ID=${DRONE_GITEA_CLIENT_ID} # - DRONE_GITEA_CLIENT_SECRET=${DRONE_GITEA_CLIENT_SECRET} - DRONE_LOGS_PRETTY=true - DRONE_LOGS_COLOR=true

    drone-runner: image: drone/drone-runner-docker:1 container_name: drone-runner 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-01

    volumes: drone-data: EOF

    Key points:

    • The server binds only to 127.0.0.1:8080 -- Nginx will terminate TLS on 443 and proxy to it.
    • The runner mounts the Docker socket so it can launch pipeline step containers on the host.
    • DRONE_RUNNER_CAPACITY=2 lets the runner process two pipelines in parallel. Tune this to your RAM.
    • The runner talks to the server over plain HTTP on the Docker network -- public traffic is still TLS via Nginx.
    Bring the stack up (but do not open it to the public yet -- Nginx comes next):

    bash
    cd /opt/drone
    docker compose up -d

    Check the logs:

    bash
    docker compose logs -f drone-server

    You should see:

    text
    INFO[0000] starting the http server     port=:80 proto=https ...
    INFO[0000] main: server listening on :80

    Press Ctrl+C to stop tailing.

    Step 5: Configure Nginx with TLS

    Install Nginx and Certbot:

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

    Create the Nginx site:

    bash
    sudo tee /etc/nginx/sites-available/drone > /dev/null <<'EOF'
    server {
        listen 80;
        server_name drone.yourdomain.com;

    location /.well-known/acme-challenge/ { root /var/www/html; }

    location / { return 301 https://$host$request_uri; } }

    server { listen 443 ssl http2; server_name drone.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/drone.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/drone.yourdomain.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5;

    add_header X-Content-Type-Options nosniff; add_header X-Frame-Options SAMEORIGIN; add_header Strict-Transport-Security "max-age=31536000" always;

    client_max_body_size 100m;

    location / { proxy_pass http://127.0.0.1:8080; 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;

    # WebSocket support for live log streaming proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";

    proxy_read_timeout 3600s; proxy_send_timeout 3600s; } } EOF

    Enable the site and obtain a certificate:

    bash
    sudo ln -s /etc/nginx/sites-available/drone /etc/nginx/sites-enabled/
    sudo rm -f /etc/nginx/sites-enabled/default
    sudo nginx -t
    sudo systemctl reload nginx

    sudo certbot --nginx -d drone.yourdomain.com --non-interactive --agree-tos -m [email protected]

    Certbot automatically rewrites the config to use the issued certificate and sets up a systemd timer for renewal.

    Verify HTTPS works:

    bash
    curl -I https://drone.yourdomain.com

    Expected:

    text
    HTTP/2 200
    server: nginx
    ...

    Step 6: Launch Drone and Activate a Repository

    Open https://drone.yourdomain.com in a browser. You will be redirected to GitHub (or Gitea) for OAuth approval. Authorize the app and Drone will log you in.

    On first login, Drone syncs your accessible repositories. Click Sync in the top-right if the list is empty. Find a repository you want to build and toggle it Active.

    Once activated, Drone registers a webhook on the repo pointing to https://drone.yourdomain.com/hook. From now on, every push or pull request sends an event, and Drone will look for a .drone.yml in the commit to decide what to run.

    Step 7: Write Your First .drone.yml

    A .drone.yml is a YAML file at the repo root that describes one or more pipelines. Here is a minimal pipeline that runs unit tests on every push for a Node project:

    yaml
    ---
    kind: pipeline
    type: docker
    name: default

    steps: - name: install image: node:20-alpine commands: - npm ci

    - name: lint image: node:20-alpine commands: - npm run lint

    - name: test image: node:20-alpine commands: - npm test

    trigger: branch: - main event: - push - pull_request

    Commit this file, push, and watch the build run in the Drone UI. Each step launches a fresh node:20-alpine container, shares a working directory with the checked-out code, and runs the commands in order. A step failure stops the pipeline (unless failure: ignore is set).

    Anatomy of a Pipeline

    • kind: pipeline -- the top-level object. Every YAML document is a pipeline.
    • type: docker -- use the Docker runtime. Other types exist (exec, kubernetes, ssh) but Docker is the standard.
    • name -- any identifier unique within the file.
    • steps -- the ordered list of containers to run.
    • trigger -- optional filter that restricts when the pipeline runs.

    Multi-Pipeline Files

    You can define several pipelines in one file separated by ---. Drone runs them in parallel by default, respecting depends_on:

    yaml
    ---
    kind: pipeline
    type: docker
    name: test

    steps: - name: unit-tests image: node:20-alpine commands: - npm ci - npm test

    kind: pipeline type: docker name: build

    depends_on: - test

    steps: - name: build image: node:20-alpine commands: - npm ci - npm run build

    This runs test first, then build only if tests pass.

    Step 8: Triggers, Conditions, and Image Promotion

    Drone's trigger and when blocks let you fine-tune when pipelines and steps run.

    Per-Pipeline Trigger

    Run a pipeline only on tags for releases:

    yaml
    trigger:
      event:
        - tag
      ref:
        - refs/tags/v*

    Run on pull requests only:

    yaml
    trigger:
      event:
        - pull_request

    Skip pipelines for specific branch names:

    yaml
    trigger:
      branch:
        exclude:
          - wip/**

    Per-Step Conditions

    Use when: on individual steps to make them conditional:

    yaml
    steps:
      - name: build
        image: node:20-alpine
        commands:
          - npm ci
          - npm run build

    - name: deploy-staging image: appleboy/drone-ssh settings: host: staging.yourdomain.com username: deploy key: from_secret: ssh_key script: - /opt/app/deploy.sh when: branch: main event: push

    - name: deploy-production image: appleboy/drone-ssh settings: host: prod.yourdomain.com username: deploy key: from_secret: ssh_key script: - /opt/app/deploy.sh when: event: promote target: production

    Image Promotion

    The promote event is Drone's built-in way to move a previously built artifact from staging to production without rebuilding. Promotions are triggered from the CLI or UI and run only the steps with a matching target:

    bash
    # Install the Drone CLI once
    curl -L https://github.com/harness/drone-cli/releases/latest/download/drone_linux_amd64.tar.gz | tar zx
    sudo install -t /usr/local/bin drone

    Authenticate (grab your token from the UI: User -> Profile)

    export DRONE_SERVER=https://drone.yourdomain.com export DRONE_TOKEN=your_user_token

    Promote build 42 on the main branch to production

    drone build promote yourorg/yourrepo 42 production

    This re-runs the pipeline with DRONE_BUILD_EVENT=promote and DRONE_DEPLOY_TO=production, so only the deploy-production step executes. The image or artifact produced by build 42 is deployed untouched -- exactly what you validated in staging.

    Building and Pushing Docker Images

    The plugins/docker image handles builds end-to-end. Here is a typical release pipeline:

    yaml
    ---
    kind: pipeline
    type: docker
    name: build-and-publish

    steps: - name: build-image image: plugins/docker settings: repo: ghcr.io/yourorg/yourapp registry: ghcr.io username: from_secret: ghcr_username password: from_secret: ghcr_token tags: - latest - ${DRONE_COMMIT_SHA:0:8} - ${DRONE_TAG} dockerfile: Dockerfile when: event: - push - tag

    trigger: branch: - main event: - push - tag

    The plugin builds the Dockerfile, logs in to the registry with your secrets, and pushes the image with three tags: latest, the short commit SHA, and the git tag (only set when the event is a tag).

    Step 9: Secrets Management

    Never commit secrets to the repo. Drone stores them in the server database, encrypted per repo or per organization, and injects them at runtime.

    Add a Repo Secret via UI

  • Open your repo in Drone.
  • Go to Settings -> Secrets -> New Secret.
  • Enter a name (e.g. ghcr_token) and the value.
  • Optionally restrict to specific events (e.g. only available on push and tag, not pull_request -- important to prevent PRs from upstream forks leaking secrets).
  • Reference the secret in .drone.yml:

    yaml
    password:
      from_secret: ghcr_token

    Add Secrets with the CLI

    bash
    drone secret add \
      --repository yourorg/yourrepo \
      --name ssh_key \
      --data "$(cat ~/.ssh/deploy_key)"

    Organization Secrets

    For secrets shared across many repos (a registry token, a Slack webhook), create them at the org level:

    bash
    drone orgsecret add yourorg SLACK_WEBHOOK "https://hooks.slack.com/services/..."

    Any repo under yourorg can reference SLACK_WEBHOOK with from_secret.

    Pull-Request Safety

    By default, secrets are not exposed to pipelines triggered by pull requests from forks. This prevents a malicious PR from exfiltrating your registry password via echo $SECRET. Never disable this protection unless you fully control all contributors.

    Step 10: Scale Runners Horizontally

    A single runner on the server VPS is enough for small teams. When build queues grow, add runners.

    Add a Second Runner on the Same Host

    Edit /opt/drone/docker-compose.yml and add another runner block:

    yaml
    drone-runner-2:
        image: drone/drone-runner-docker:1
        container_name: drone-runner-2
        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-02
    bash
    cd /opt/drone
    docker compose up -d

    Add a Runner on a Separate VPS

    On a second VPS (fresh Ubuntu 24.04 with Docker), create /opt/drone-runner/docker-compose.yml:

    yaml
    version: "3.8"

    services: drone-runner: image: drone/drone-runner-docker:1 container_name: drone-runner restart: unless-stopped volumes: - /var/run/docker.sock:/var/run/docker.sock environment: - DRONE_RPC_PROTO=https - DRONE_RPC_HOST=drone.yourdomain.com - DRONE_RPC_SECRET=paste_the_same_secret_here - DRONE_RUNNER_CAPACITY=4 - DRONE_RUNNER_NAME=runner-big-01

    bash
    docker compose up -d

    The runner connects over HTTPS to the Drone server, registers itself, and starts picking up jobs. You can provision dedicated runner VPSes with more CPU and RAM for heavy jobs, or even a GPU VPS for ML test suites -- the server sees them all as a pool.

    Targeting Specific Runners

    Use labels to route pipelines:

    On the runner:

    yaml
    environment:
      - DRONE_RUNNER_LABELS=gpu:true,arch:amd64

    In .drone.yml:

    yaml
    node:
      gpu: "true"

    Only runners with the matching label will accept the pipeline.

    Performance and Hardening

    Resource Limits per Step

    Cap CPU and memory so a runaway step does not crash the host:

    yaml
    steps:
      - name: build
        image: golang:1.22
        commands: [go build ./...]
        resources:
          limits:
            cpu: 2000      # 2 CPUs
            memory: 2GiB

    Prune Docker Regularly

    Build caches and intermediate images accumulate fast. Add a weekly cron:

    bash
    sudo tee /etc/cron.weekly/drone-docker-prune > /dev/null <<'EOF'
    #!/bin/bash
    docker system prune -af --filter "until=168h"
    docker volume prune -f
    EOF
    sudo chmod +x /etc/cron.weekly/drone-docker-prune

    Back Up Drone Data

    The drone-data volume contains the SQLite database with users, secrets, and build history. Back it up regularly:

    bash
    sudo tar czf /root/drone-backup-$(date +%F).tar.gz -C /var/lib/docker/volumes/drone_drone-data/_data .

    Ship the archive off-server with rsync or restic.

    Upgrade Drone

    Drone follows semver and publishes minor releases regularly. Upgrade by pulling and recreating:

    bash
    cd /opt/drone
    docker compose pull
    docker compose up -d

    Always check the release notes at github.com/harness/drone/releases before a major upgrade.

    Restrict OAuth Scope

    For GitHub, the OAuth app grants repo scope by default, which is broad. If your org uses GitHub Apps, install a Drone GitHub App with fine-grained repository selection instead of a personal OAuth app.

    Troubleshooting

    ProblemCauseSolution
    Webhooks from GitHub time outDrone not reachable on HTTPSCheck curl -I https://drone.yourdomain.com. Confirm the A record, firewall rules (80/443), and Nginx is running.
    401 Unauthorized in runner logsRPC secret mismatchEnsure DRONE_RPC_SECRET is identical in both server and every runner. Restart both after editing.
    Build stuck in "pending" foreverNo runner connected, or label mismatchIn the UI: System -> Runners. If empty, check runner logs: docker compose logs drone-runner.
    OAuth login loops on /loginCallback URL mismatchThe URL registered in GitHub/Gitea must exactly match ${DRONE_SERVER_PROTO}://${DRONE_SERVER_HOST}/login.
    Error: no space left on deviceDocker consumed all diskPrune: docker system prune -af --volumes. Move /var/lib/docker to a bigger disk.
    Step fails with Get "https://registry-1.docker.io/v2/": net/http: TLS handshake timeoutDocker Hub rate limiting on the VPS IPAuthenticate with a Docker Hub account in the runner: add DRONE_RUNNER_ENVIRON with DOCKER_USERNAME and DOCKER_PASSWORD, or use a pull-through mirror.
    Secrets empty inside PR from forkExpected behaviorSecrets are withheld from fork PRs by design. Restrict the PR pipeline to non-secret steps only.
    Live logs stop streaming in the UINginx bufferingEnsure proxy_buffering off and WebSocket upgrade headers are set (as in Step 5).

    Useful Log Commands

    Server logs:

    bash
    docker compose -f /opt/drone/docker-compose.yml logs -f drone-server

    Runner logs:

    bash
    docker compose -f /opt/drone/docker-compose.yml logs -f drone-runner

    Follow a specific build's containers while it runs:

    bash
    docker ps --filter "label=io.drone=true"

    FAQ

    Is Drone CI still maintained after the Harness acquisition?

    Yes. Harness acquired Drone in 2020 and continues to publish the open-source drone/drone and drone-runner-docker images under the Apache 2.0 license. Self-hosted installs remain fully functional, receive security updates, and work identically to pre-acquisition builds. The commercial Harness platform is a separate SaaS product; nothing is forced on self-hosted users. The GitHub repositories live at github.com/harness/drone and see regular commits.

    What is the difference between Drone CI and Woodpecker CI?

    Woodpecker is a community fork of Drone that started shortly before the Harness acquisition over concerns about the project's future. The YAML syntax is nearly identical, and migrating a .drone.yml to .woodpecker.yml usually takes minutes. Drone has more mature enterprise features (fine-grained RBAC, more plugins, commercial support if you ever want it), while Woodpecker is community-governed and tends to ship new syntax faster. Both run well on a single VPS -- the choice often comes down to whether you prefer corporate stewardship (Drone) or pure community governance (Woodpecker).

    How much RAM does Drone CI need?

    The drone/drone server uses around 200-400 MB of RAM at rest. The real memory cost comes from the runner and the pipeline steps it launches. A single Node or Go build typically uses 500 MB to 2 GB per step. Multiply by your DRONE_RUNNER_CAPACITY. For light use (one runner, capacity 2), 4 GB RAM is enough. For parallel builds with Docker-in-Docker or heavier toolchains (Rust, C++), plan for 8-12 GB. The CloudCore Professional at 12 GB is the sweet spot.

    Can I run Drone CI without a public domain?

    Technically yes, but the OAuth handshake requires your Git provider to reach Drone's callback URL. If Drone is not internet-reachable, the easiest path is a self-hosted Gitea on the same private network -- both Gitea and Drone can live entirely behind a firewall or on a WireGuard mesh. Alternatively, use Cloudflare Tunnel to expose Drone without opening inbound ports. For most teams, a public domain with HTTPS is the path of least resistance.

    Does each runner need its own VPS?

    No. You can run the server and a runner on the same VPS for small teams -- the setup in this guide does exactly that. Scale horizontally only when build queues grow. Add extra drone-runner-docker containers on the same host (cheap, more parallelism on one box) or on separate VPSes (true isolation, bigger pool, specialized hardware). Each runner reports capacity independently, so you can mix a 2 vCPU small runner with a 16 vCPU heavy runner and Drone will distribute work appropriately.

    How do I migrate from Jenkins or GitHub Actions?

    Drone's YAML is much simpler than Jenkinsfile Groovy and similar in spirit to GitHub Actions workflows. Most migrations follow this pattern: list each Jenkins stage or Actions job as a Drone step, pick an appropriate public image (node:20-alpine, golang:1.22, python:3.12), translate shell commands as-is, and replace proprietary plugins with Drone plugins (plugins/docker, plugins/s3, plugins/slack). Matrix builds become multiple pipelines with depends_on. The biggest mindset change is that every step is a container, not a shell snippet on a persistent agent -- so install tooling by picking the right base image, not by running apt install in the pipeline.

    Can Drone build itself (Docker-in-Docker)?

    Yes, but with care. The plugins/docker image already knows how to talk to the host's Docker daemon via the mounted socket, which is the recommended approach. If you genuinely need a nested daemon (for Kubernetes-in-Docker testing, for example), use the docker:dind image as a service inside the pipeline. Mounting the host socket is faster and uses less RAM, but any step with socket access effectively has root on the host -- so only run trusted pipelines that way, and keep untrusted code in strictly non-privileged steps.

    Next Steps

    With Drone CI running, here are productive directions to take next:

    • Install Gitea alongside Drone -- Run your own Git server and CI on the same network for a fully self-hosted dev loop. See How to Install Gitea on Ubuntu.
    • Compare with Woodpecker CI -- If you want a community-governed fork of the same YAML, read How to Install Woodpecker CI on Ubuntu and decide which suits your team.
    • Harden your Docker host -- Drone relies on Docker for every pipeline. Make sure the host is locked down: see How to Install Docker on Ubuntu for baseline hardening steps.
    • Add a local Docker registry -- Run a private registry on the same VPS to avoid Docker Hub rate limits and keep internal images off the public internet.
    • Wire up Slack or Discord notifications -- Drop the plugins/slack or plugins/discord step into pipelines to get build status in chat.
    • Read the official docs -- docs.drone.io covers every YAML directive, plugin, and runtime in depth.

    Need a VPS sized right for CI/CD?
    >
    The CloudCore Professional plan is the sweet spot for Drone: 6 vCPU, 12 GB RAM, and 100 GB NVMe for EUR 19.99/month. Deploy in under 60 seconds with Ubuntu 24.04 pre-installed and run unlimited builds on your own infrastructure.
    >
    Launch Your CI/CD VPS Now

    Was this article helpful?

    ← Back to Install GuidesBrowse all categories →

    Still have questions?

    Contact Support →Submit a Ticket