Technology Encyclopedia Home >Deploy Drone CI on a Cloud Server — Lightweight Container-Native CI/CD for Your Git Repos

Deploy Drone CI on a Cloud Server — Lightweight Container-Native CI/CD for Your Git Repos

I ran Jenkins for a couple of years. It works, but configuring it for a new project involves plugins, Groovy syntax, XML configuration, and a learning curve that doesn't feel proportional to what I'm trying to do.

Drone CI is the opposite. The pipeline is a YAML file in your repo, every step runs in a Docker container (so you just reference an image — no plugin installation), and the whole thing integrates with GitHub or Gitea via OAuth in about an hour.

I switched several personal projects to Drone after getting tired of the Jenkins maintenance overhead. For a self-hosted setup where you want something that works cleanly without being heavyweight, it's the right tool.

I switched one of my projects to Drone after finding it noticeably faster to configure and less resource-hungry than Jenkins. The whole setup from install to first pipeline run took about an hour.

I run Drone CI on Tencent Cloud Lighthouse. The 2 GB RAM / 2 vCPU plan handles Drone server and a runner comfortably. For heavy parallel builds, 4 GB RAM gives more headroom. The snapshot feature is valuable for CI/CD infrastructure — a snapshot before a Drone version upgrade means you can restore quickly if the upgrade breaks existing pipelines. Running Drone on the same server as Gitea (if you're using it) keeps the OAuth roundtrip latency low and the configuration simple.


Table of Contents

  1. Why Drone CI?
  2. What You Need
  3. Part 1: Set Up OAuth in Your Git Provider
  4. Part 2: Install Drone Server
  5. Part 3: Install Drone Runner
  6. Part 4: Write Your First Pipeline
  7. Part 5: Secrets Management
  8. Part 6: Deploy Steps
  9. The Thing That Tripped Me Up
  10. Troubleshooting
  11. Summary

  • Key Takeaways
  • Use the appropriate Lighthouse application image to skip manual installation steps where available
  • Lighthouse snapshots provide one-click full-server backup before major changes
  • OrcaTerm browser terminal lets you manage the server from any device
  • CBS cloud disk expansion handles growing storage needs without server migration
  • Console-level firewall + UFW = two independent protection layers

Why Drone CI? {#why}

Drone is designed for simplicity and container-native workflows:

  • Every step is a container — no plugin installation, just reference any Docker image
  • Pipeline as code.drone.yml lives with your code, versioned in Git
  • Lightweight — Drone server uses ~50 MB RAM at idle
  • Multi-pipeline support — run different pipelines for different branches or events
  • Built-in secrets — encrypted environment variables stored server-side
  • Gitea integration — works natively with self-hosted Gitea (good complement to earlier guide)

Compared to GitHub Actions: Drone runs on your own server, so no minute limits. Compared to Jenkins: significantly simpler configuration and much lower resource usage.


What You Need {#prerequisites}

Requirement Details
Server Ubuntu 22.04, 2 GB RAM
Docker Installed
Git provider GitHub, Gitea, GitLab, or Bitbucket
Domain For Drone server HTTPS
OAuth app Created in your Git provider (see Part 1)

Part 1: Set Up OAuth in Your Git Provider {#part-1}

Drone authenticates users via OAuth from your Git provider.

For GitHub:

  1. Go to GitHub → Settings → Developer settings → OAuth Apps → New OAuth App
  2. Application name: Drone CI
  3. Homepage URL: https://drone.yourdomain.com
  4. Authorization callback URL: https://drone.yourdomain.com/login
  5. Click Register application

Note the Client ID and generate a Client Secret — you'll need both.

For Gitea:

  1. In Gitea, go to User Settings → Applications → Manage OAuth2 Applications
  2. Application name: Drone CI
  3. Redirect URI: https://drone.yourdomain.com/login
  4. Click Create Application

Note the Client ID and Client Secret.


Part 2: Install Drone Server {#part-2}

2.1 — Generate a Shared Secret

Drone server and runners use a shared secret for communication:

openssl rand -hex 16
# Save this output — you'll use it for both server and runner config

2.2 — Create docker-compose.yml

Create /opt/drone/docker-compose.yml:

mkdir -p /opt/drone
nano /opt/drone/docker-compose.yml

For GitHub:

version: '3.8'

services:
  drone-server:
    image: drone/drone:2
    restart: always
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - /opt/drone/data:/data
    environment:
      - DRONE_GITHUB_CLIENT_ID=YOUR_GITHUB_CLIENT_ID
      - DRONE_GITHUB_CLIENT_SECRET=YOUR_GITHUB_CLIENT_SECRET
      - DRONE_RPC_SECRET=YOUR_SHARED_SECRET
      - DRONE_SERVER_HOST=drone.yourdomain.com
      - DRONE_SERVER_PROTO=https
      - DRONE_USER_CREATE=username:your-github-username,admin:true
      - DRONE_LOGS_DEBUG=false

For Gitea, replace the GitHub environment variables with:

      - DRONE_GITEA_SERVER=https://your-gitea-domain.com
      - DRONE_GITEA_CLIENT_ID=YOUR_GITEA_CLIENT_ID
      - DRONE_GITEA_CLIENT_SECRET=YOUR_GITEA_CLIENT_SECRET

2.3 — Start Drone Server

cd /opt/drone
docker compose up -d
docker compose logs -f drone-server

2.4 — Set Up Nginx with HTTPS

sudo apt install -y nginx certbot python3-certbot-nginx
sudo nano /etc/nginx/sites-available/drone
server {
    listen 80;
    server_name drone.yourdomain.com;

    location / {
        proxy_pass http://localhost: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;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
sudo ln -s /etc/nginx/sites-available/drone /etc/nginx/sites-enabled/
sudo certbot --nginx -d drone.yourdomain.com

Navigate to https://drone.yourdomain.com. Click Login — it redirects to GitHub/Gitea for OAuth authorization.


Part 3: Install Drone Runner {#part-3}

The runner executes pipeline steps on Docker containers. Install it on the same server:

Add to your docker-compose.yml:

  drone-runner:
    image: drone/drone-runner-docker:1
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - DRONE_RPC_PROTO=https
      - DRONE_RPC_HOST=drone.yourdomain.com
      - DRONE_RPC_SECRET=YOUR_SHARED_SECRET
      - DRONE_RUNNER_CAPACITY=2
      - DRONE_RUNNER_NAME=my-vps-runner
    depends_on:
      - drone-server
cd /opt/drone
docker compose up -d
docker compose logs -f drone-runner

You should see: successfully pinged the remote server.


Part 4: Write Your First Pipeline {#part-4}

Add .drone.yml to your repository:

kind: pipeline
type: docker
name: default

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

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

  - name: build
    image: node:20-alpine
    commands:
      - npm run build
    depends_on:
      - test

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

Activate the Repository in Drone

  1. Log in to https://drone.yourdomain.com
  2. Click Sync to load your repositories from GitHub/Gitea
  3. Find your repository and toggle it Active

Now push a commit — the pipeline runs automatically.


Part 5: Secrets Management {#part-5}

Don't put API keys or passwords in .drone.yml. Use Drone's built-in secrets:

In the Drone UI:

  1. Go to your repository → Settings → Secrets → Add a secret
  2. Name: deploy_key
  3. Value: (your SSH private key or API token)
  4. Click Save

Reference in your pipeline:

steps:
  - name: deploy
    image: alpine:latest
    environment:
      DEPLOY_KEY:
        from_secret: deploy_key
    commands:
      - echo "$DEPLOY_KEY" > /tmp/key
      - chmod 600 /tmp/key
      - ssh -i /tmp/key ubuntu@YOUR_SERVER "cd /opt/myapp && git pull"

Organization-level secrets (shared across repositories) are available in the Drone organization settings.


Part 6: Deploy Steps {#part-6}

SSH Deployment

Using the appleboy/ssh-action style directly in Drone:

  - name: deploy
    image: appleboy/drone-ssh
    settings:
      host: YOUR_PRODUCTION_IP
      username: ubuntu
      key:
        from_secret: ssh_private_key
      script:
        - cd /opt/myapp
        - git pull origin main
        - npm ci --production
        - pm2 reload myapp
    when:
      branch:
        - main
      event:
        - push

Docker Deploy

  - name: build-and-push
    image: plugins/docker
    settings:
      repo: registry.yourdomain.com/myapp
      tags:
        - latest
        - ${DRONE_COMMIT_SHA:0:8}
      registry: registry.yourdomain.com
      username:
        from_secret: registry_username
      password:
        from_secret: registry_password

The Thing That Tripped Me Up {#gotcha}

After getting Drone running, my pipelines were stuck in "pending" state indefinitely. They'd show in the UI but never start executing.

The cause: the Drone runner couldn't communicate with the Drone server. The runner uses the public HTTPS address (my domain), not localhost. My firewall was blocking outbound connections on port 443 from the Docker network.

The fix:

First, check if the runner is connected:

docker compose logs drone-runner | grep -i "ping\|error\|connect"

I saw: failed to connect to server.

The issue was that Docker containers by default use the docker0 bridge network, and the runner container was routing traffic through the host's external interface. My UFW rules weren't blocking it, but the Docker network wasn't resolving the domain correctly inside the container.

Solution: Tell the runner to use the container network name instead of the external domain for internal communication:

  drone-runner:
    environment:
      - DRONE_RPC_PROTO=http        # Use HTTP internally
      - DRONE_RPC_HOST=drone-server  # Use container service name

But only if both services are on the same Docker Compose network. If the runner is on a different host or network, keep the HTTPS external URL and ensure DNS resolves correctly inside containers.


Troubleshooting {#troubleshooting}

Issue Likely Cause Fix
Pipeline stuck "pending" Runner not connected Check runner logs for connection errors
OAuth login fails Redirect URI mismatch Verify callback URL matches exactly in Git provider
Build fails: Cannot connect to Docker Runner volume missing Ensure /var/run/docker.sock is mounted
Secret not available in step Wrong name reference Check exact secret name, case-sensitive
Drone UI not loading Nginx misconfigured Check proxy headers include Upgrade for WebSocket
Runner capacity exceeded All slots busy Increase DRONE_RUNNER_CAPACITY or add another runner
Git clone fails No SSH key or HTTPS token Check repository access in Drone settings

Summary {#verdict}

What you built:

  • Self-hosted Drone CI server with HTTPS
  • Docker-based runner executing all pipeline steps in containers
  • OAuth integration with GitHub or Gitea
  • Repository-specific secrets for credentials
  • Automated pipelines triggered on push and PR events
  • SSH and Docker deploy steps for production deployment

Drone's .drone.yml format is clean and readable. Adding CI/CD to a new project is a matter of dropping a config file in the repo and toggling it active in the Drone UI — under 5 minutes once the server is running.

Frequently Asked Questions {#faq}

What's the difference between CI and CD?
CI (Continuous Integration) automatically runs tests when code is pushed. CD (Continuous Deployment/Delivery) automatically deploys tested code to servers. Together they eliminate manual build-test-deploy cycles.

How do I store secrets (API keys, passwords) in Drone CI?
Use the CI/CD platform's secrets management — never put secrets in your repository. Reference them as environment variables in your pipeline config. Lighthouse server credentials go in deployment secrets.

What happens if a deployment fails partway through?
Implement a rollback strategy: tag git commits before deployment, keep the previous release available, and have a procedure to revert. Lighthouse snapshots provide a full-server rollback point.

How do I deploy to multiple environments (staging, production) from the same pipeline?
Use branch-based or tag-based triggers. Pushes to develop deploy to staging; tagged releases or merges to main deploy to production. Configure different deployment targets per environment.

How long should a CI/CD pipeline take?
Aim for under 5 minutes for feedback on a push. If builds are slow, parallelize test suites, cache dependencies, and build Docker layers efficiently. Long pipelines reduce developer productivity.

👉 Get started with Tencent Cloud Lighthouse
👉 View current pricing and launch promotions
👉 Explore all active deals and offers