The Way to Your Own Cloud (Part 12) – Let Software Build Itself

Continuous Integration and Continuous Deployment (CI/CD) are among the most important tools in modern software development.

Instead of manually building code, running tests, and performing deployments, a pipeline takes over these tasks automatically – reliably and reproducibly.

With Drone CI, there is a lightweight, container-based solution that integrates perfectly into your own cloud environment.

Especially practical: Drone can be connected directly with Gitea. This way, builds can be started immediately after each push – without having to rely on GitHub or GitLab.

Setup

Drone stores its data in SQLite by default.

First, create a working directory:

sudo mkdir -p /opt/drone

cd /opt/drone

Then, determine a random port for the service:

echo $(shuf -i 1024-65535 -n 1)

Now create a docker-compose.yaml file:

services:
  drone-server:
    image: localhost:5000/drone/drone:2
    container_name: drone-server
    restart: unless-stopped
    environment:
      - DRONE_GITEA_SERVER=https://<CLOUD-DOMAIN>:<GITEA-PORT>
      - DRONE_GITEA_CLIENT_ID=<CLIENT-ID>
      - DRONE_GITEA_CLIENT_SECRET=<CLIENT-SECRET>
      - DRONE_RPC_SECRET=<LONG-RANDOM-STRING>
      - DRONE_SERVER_HOST=<CLOUD-DOMAIN>:<DRONE-PORT>
      - DRONE_SERVER_PROTO=https
    ports:
      - "9999:80"
    volumes:
      - ./data:/data

  drone-runner:
    image: localhost:5000/drone/drone-runner-docker:1
    container_name: drone-runner
    restart: unless-stopped
    environment:
      - DRONE_RPC_PROTO=https
      - DRONE_RPC_HOST=<CLOUD-DOMAIN>:<DRONE-PORT>
      - DRONE_RPC_SECRET=<LONG-RANDOM-STRING>
      - DRONE_RUNNER_CAPACITY=2
      - DRONE_RUNNER_NAME=runner-1
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

Then load the images from the official hub into the local repository:

docker pull drone/drone:2
docker tag drone/drone:2 localhost:5000/drone/drone:2
docker push localhost:5000/drone/drone:2

docker pull drone/drone-runner-docker:1
docker tag drone/drone-runner-docker:1 localhost:5000/drone/drone-runner-docker:1
docker push localhost:5000/drone/drone-runner-docker:1

Gitea Configuration

Now Gitea must be configured to work with Drone.

Log into Gitea as Admin and create a new OAuth2 application:

  • Name: e.g. Drone CI
  • Redirect URL: https://<CLOUD-DOMAIN>:<DRONE-PORT>/login

Afterwards, you will receive a Client-ID and Client-Secret, which must be entered in the docker-compose.yaml (see above).

The shared secret (<LONG-RANDOM-STRING>) for communication can easily be generated with:

openssl rand -hex 32

NGINX Configuration

For external use, Drone also requires an NGINX configuration.

File /etc/nginx/sites-available/drone:

server {
    listen <RANDOM-PORT> ssl;
    server_name <CLOUD-DOMAIN>;

    ssl_certificate /etc/letsencrypt/live/<CLOUD-DOMAIN>/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/<CLOUD-DOMAIN>/privkey.pem;

    location / {
        proxy_pass http://localhost:9999;
        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;
    }
}

Activate and restart:

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

Open the Drone port in the firewall:

sudo ufw allow <RANDOM-PORT>
sudo ufw reload

Backup Integration

As mentioned earlier, Drone uses an SQLite database in the directory /opt/drone/data.
This can be integrated into the existing backup system with a simple plugin.

Create the file /opt/backup/plugins/drone.sh:

#!/bin/bash

PLUGIN_OUTPUT_DIR="$1"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S.%3N')] $*"
}

DRONE_DATA_DIR="/opt/drone/data"
BACKUP_DEST="$PLUGIN_OUTPUT_DIR/drone_$(date '+%Y-%m-%d').tar.gz"

log "[INFO] Starting backup of Drone data..."
tar -czf "$BACKUP_DEST" -C "$DRONE_DATA_DIR" .

if [ $? -eq 0 ]; then
    log "[INFO] Backup was successful: $BACKUP_DEST"
else
    log "[ERROR] Backup failed!"
fi

Make it executable:

chmod +x /opt/backup/plugins/drone.sh

Conclusion

With Drone CI you now have a fully functional CI/CD pipeline in your own cloud.

Thanks to the integration with Gitea, builds start automatically after each commit.

Your build resources now depend solely on your own server.

Quick & Dirty

DOMAIN='cloud.example.com'
GITEA_PORT=12345  # adjust accordingly!
DRONE_PORT=$(shuf -i 1024-65535 -n 1)

SECRET=$(openssl rand -hex 32)

CLIENT_ID='YOUR-GITEA-CLIENT-ID'
CLIENT_SECRET='YOUR-GITEA-CLIENT-SECRET'

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

cat <<EOF > docker-compose.yaml
services:
  drone-server:
    image: localhost:5000/drone/drone:2
    container_name: drone-server
    restart: unless-stopped
    environment:
      - DRONE_GITEA_SERVER=https://$DOMAIN:$GITEA_PORT
      - DRONE_GITEA_CLIENT_ID=$CLIENT_ID
      - DRONE_GITEA_CLIENT_SECRET=$CLIENT_SECRET
      - DRONE_RPC_SECRET=$SECRET
      - DRONE_SERVER_HOST=$DOMAIN:$DRONE_PORT
      - DRONE_SERVER_PROTO=https
    ports:
      - "9999:80"
    volumes:
      - ./data:/data

  drone-runner:
    image: localhost:5000/drone/drone-runner-docker:1
    container_name: drone-runner
    restart: unless-stopped
    environment:
      - DRONE_RPC_PROTO=https
      - DRONE_RPC_HOST=$DOMAIN:$DRONE_PORT
      - DRONE_RPC_SECRET=$SECRET
      - DRONE_RUNNER_CAPACITY=2
      - DRONE_RUNNER_NAME=runner-1
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
EOF

docker pull drone/drone:2 && docker tag drone/drone:2 localhost:5000/drone/drone:2 && docker push localhost:5000/drone/drone:2
docker pull drone/drone-runner-docker:1 && docker tag drone/drone-runner-docker:1 localhost:5000/drone/drone-runner-docker:1 && docker push localhost:5000/drone/drone-runner-docker:1

docker compose up -d

sudo tee /etc/nginx/sites-available/drone >/dev/null <<EONGX
server {
    listen $DRONE_PORT ssl;
    server_name $DOMAIN;

    ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem;

    location / {
        proxy_pass http://localhost:9999;
        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;
    }
}
EONGX

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

sudo ufw allow $DRONE_PORT && sudo ufw reload