The way to an own clowd - Part 12
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