$docker.recipes

Drone CI with Gitea

advanced

Container-native CI/CD with Drone, Gitea integration, Docker runner, and secrets management.

[i]Overview

Drone is a container-native continuous integration platform built on Docker that transforms Git commits into automated testing and deployment pipelines. Originally created by Brad Rydzewski in 2014, Drone has evolved into a powerful CI/CD system that executes pipeline steps inside ephemeral Docker containers, providing true isolation and reproducibility. Unlike traditional CI systems that require agents with pre-installed tools, Drone's container-first approach means every build runs in a clean environment with exactly the dependencies specified in your pipeline configuration. This stack combines Drone with Gitea, a lightweight self-hosted Git service that provides GitHub-like functionality without the resource overhead of GitLab or enterprise solutions. Together, they create a complete DevOps platform where code repositories, issue tracking, and automated pipelines operate within your own infrastructure. The integration between Gitea and Drone enables automatic webhook-triggered builds, pull request validation, and deployment automation while maintaining complete control over your source code and build artifacts. This combination is particularly valuable for organizations requiring air-gapped development environments, teams with strict data sovereignty requirements, or developers who want enterprise-grade CI/CD capabilities without recurring SaaS costs.

[*]Key Features

  • [+]Container-native pipeline execution with Docker-in-Docker support for isolated build environments
  • [+]Native Gitea integration with OAuth2 authentication and automatic webhook configuration
  • [+]YAML-based pipeline definitions with multi-stage builds, parallel execution, and conditional steps
  • [+]Docker registry integration for automated image building, tagging, and deployment
  • [+]Encrypted secrets management with per-repository and organization-level secret storage
  • [+]Real-time build logs with terminal output streaming and downloadable artifacts
  • [+]Pull request automation with status checks, approval workflows, and merge conflict detection
  • [+]Multi-architecture build support for ARM, AMD64, and custom runner configurations

[#]Common Use Cases

  • [1]Self-hosted development teams requiring private Git hosting with integrated CI/CD pipelines
  • [2]Organizations with compliance requirements needing air-gapped CI/CD infrastructure
  • [3]Microservices architectures requiring automated Docker image builds and registry management
  • [4]Open source projects needing cost-effective CI/CD without GitHub Actions minute limitations
  • [5]Development teams practicing GitOps with automated testing and deployment to Kubernetes clusters
  • [6]Startups and small companies wanting GitHub Enterprise features at zero recurring cost
  • [7]Educational institutions teaching DevOps practices with self-contained development environments

[!]Prerequisites

  • [!]Minimum 2GB RAM recommended (512MB for Gitea, 1GB+ for PostgreSQL instances, plus runner overhead)
  • [!]Docker Engine with BuildKit support and docker.sock access for the Drone runner
  • [!]Available ports 3000 (Gitea web), 222 (Gitea SSH), and 8080 (Drone web interface)
  • [!]Domain names or static IPs for DRONE_SERVER_HOST configuration and webhook callbacks
  • [!]Understanding of YAML pipeline syntax and Docker container concepts for build configuration
  • [!]SSL certificates for production deployments to enable secure webhook delivery
[!]

WARNING: For development & testing. Review security settings, change default credentials, and test thoroughly before production use. See Terms

[$]docker-compose.yml

[docker-compose.yml]
1services:
2 gitea:
3 image: gitea/gitea:latest
4 environment:
5 USER_UID: 1000
6 USER_GID: 1000
7 GITEA__database__DB_TYPE: postgres
8 GITEA__database__HOST: gitea-db:5432
9 GITEA__database__NAME: gitea
10 GITEA__database__USER: ${POSTGRES_USER}
11 GITEA__database__PASSWD: ${POSTGRES_PASSWORD}
12 ports:
13 - "3000:3000"
14 - "222:22"
15 volumes:
16 - gitea_data:/data
17 depends_on:
18 gitea-db:
19 condition: service_healthy
20 networks:
21 - drone-net
22 restart: unless-stopped
23
24 gitea-db:
25 image: postgres:15-alpine
26 environment:
27 POSTGRES_USER: ${POSTGRES_USER}
28 POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
29 POSTGRES_DB: gitea
30 volumes:
31 - gitea_postgres:/var/lib/postgresql/data
32 healthcheck:
33 test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
34 interval: 10s
35 timeout: 5s
36 retries: 5
37 networks:
38 - drone-net
39 restart: unless-stopped
40
41 drone:
42 image: drone/drone:latest
43 environment:
44 DRONE_GITEA_SERVER: http://gitea:3000
45 DRONE_GITEA_CLIENT_ID: ${DRONE_GITEA_CLIENT_ID}
46 DRONE_GITEA_CLIENT_SECRET: ${DRONE_GITEA_CLIENT_SECRET}
47 DRONE_RPC_SECRET: ${DRONE_RPC_SECRET}
48 DRONE_SERVER_HOST: ${DRONE_SERVER_HOST}
49 DRONE_SERVER_PROTO: http
50 DRONE_DATABASE_DRIVER: postgres
51 DRONE_DATABASE_DATASOURCE: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@drone-db:5432/drone?sslmode=disable
52 ports:
53 - "8080:80"
54 volumes:
55 - drone_data:/data
56 depends_on:
57 drone-db:
58 condition: service_healthy
59 gitea:
60 condition: service_started
61 networks:
62 - drone-net
63 restart: unless-stopped
64
65 drone-db:
66 image: postgres:15-alpine
67 environment:
68 POSTGRES_USER: ${POSTGRES_USER}
69 POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
70 POSTGRES_DB: drone
71 volumes:
72 - drone_postgres:/var/lib/postgresql/data
73 healthcheck:
74 test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
75 interval: 10s
76 timeout: 5s
77 retries: 5
78 networks:
79 - drone-net
80 restart: unless-stopped
81
82 drone-runner:
83 image: drone/drone-runner-docker:latest
84 environment:
85 DRONE_RPC_PROTO: http
86 DRONE_RPC_HOST: drone
87 DRONE_RPC_SECRET: ${DRONE_RPC_SECRET}
88 DRONE_RUNNER_CAPACITY: 2
89 DRONE_RUNNER_NAME: docker-runner
90 volumes:
91 - /var/run/docker.sock:/var/run/docker.sock
92 depends_on:
93 - drone
94 networks:
95 - drone-net
96 restart: unless-stopped
97
98volumes:
99 gitea_data:
100 gitea_postgres:
101 drone_data:
102 drone_postgres:
103
104networks:
105 drone-net:
106 driver: bridge

[$].env Template

[.env]
1# PostgreSQL (shared)
2POSTGRES_USER=drone
3POSTGRES_PASSWORD=secure_postgres_password
4
5# Drone Server
6DRONE_SERVER_HOST=localhost:8080
7DRONE_RPC_SECRET=your_rpc_secret_here
8
9# Gitea OAuth2 (create in Gitea Settings > Applications)
10DRONE_GITEA_CLIENT_ID=your_client_id
11DRONE_GITEA_CLIENT_SECRET=your_client_secret

[i]Usage Notes

  1. [1]Gitea at http://localhost:3000
  2. [2]Drone at http://localhost:8080
  3. [3]Create OAuth2 app in Gitea first
  4. [4]Add .drone.yml to repos for pipelines

Individual Services(5 services)

Copy individual services to mix and match with your existing compose files.

gitea
gitea:
  image: gitea/gitea:latest
  environment:
    USER_UID: 1000
    USER_GID: 1000
    GITEA__database__DB_TYPE: postgres
    GITEA__database__HOST: gitea-db:5432
    GITEA__database__NAME: gitea
    GITEA__database__USER: ${POSTGRES_USER}
    GITEA__database__PASSWD: ${POSTGRES_PASSWORD}
  ports:
    - "3000:3000"
    - "222:22"
  volumes:
    - gitea_data:/data
  depends_on:
    gitea-db:
      condition: service_healthy
  networks:
    - drone-net
  restart: unless-stopped
gitea-db
gitea-db:
  image: postgres:15-alpine
  environment:
    POSTGRES_USER: ${POSTGRES_USER}
    POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    POSTGRES_DB: gitea
  volumes:
    - gitea_postgres:/var/lib/postgresql/data
  healthcheck:
    test:
      - CMD-SHELL
      - pg_isready -U ${POSTGRES_USER}
    interval: 10s
    timeout: 5s
    retries: 5
  networks:
    - drone-net
  restart: unless-stopped
drone
drone:
  image: drone/drone:latest
  environment:
    DRONE_GITEA_SERVER: http://gitea:3000
    DRONE_GITEA_CLIENT_ID: ${DRONE_GITEA_CLIENT_ID}
    DRONE_GITEA_CLIENT_SECRET: ${DRONE_GITEA_CLIENT_SECRET}
    DRONE_RPC_SECRET: ${DRONE_RPC_SECRET}
    DRONE_SERVER_HOST: ${DRONE_SERVER_HOST}
    DRONE_SERVER_PROTO: http
    DRONE_DATABASE_DRIVER: postgres
    DRONE_DATABASE_DATASOURCE: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@drone-db:5432/drone?sslmode=disable
  ports:
    - "8080:80"
  volumes:
    - drone_data:/data
  depends_on:
    drone-db:
      condition: service_healthy
    gitea:
      condition: service_started
  networks:
    - drone-net
  restart: unless-stopped
drone-db
drone-db:
  image: postgres:15-alpine
  environment:
    POSTGRES_USER: ${POSTGRES_USER}
    POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    POSTGRES_DB: drone
  volumes:
    - drone_postgres:/var/lib/postgresql/data
  healthcheck:
    test:
      - CMD-SHELL
      - pg_isready -U ${POSTGRES_USER}
    interval: 10s
    timeout: 5s
    retries: 5
  networks:
    - drone-net
  restart: unless-stopped
drone-runner
drone-runner:
  image: drone/drone-runner-docker:latest
  environment:
    DRONE_RPC_PROTO: http
    DRONE_RPC_HOST: drone
    DRONE_RPC_SECRET: ${DRONE_RPC_SECRET}
    DRONE_RUNNER_CAPACITY: 2
    DRONE_RUNNER_NAME: docker-runner
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock
  depends_on:
    - drone
  networks:
    - drone-net
  restart: unless-stopped

[>]Quick Start

[terminal]
1# 1. Create the compose file
2cat > docker-compose.yml << 'EOF'
3services:
4 gitea:
5 image: gitea/gitea:latest
6 environment:
7 USER_UID: 1000
8 USER_GID: 1000
9 GITEA__database__DB_TYPE: postgres
10 GITEA__database__HOST: gitea-db:5432
11 GITEA__database__NAME: gitea
12 GITEA__database__USER: ${POSTGRES_USER}
13 GITEA__database__PASSWD: ${POSTGRES_PASSWORD}
14 ports:
15 - "3000:3000"
16 - "222:22"
17 volumes:
18 - gitea_data:/data
19 depends_on:
20 gitea-db:
21 condition: service_healthy
22 networks:
23 - drone-net
24 restart: unless-stopped
25
26 gitea-db:
27 image: postgres:15-alpine
28 environment:
29 POSTGRES_USER: ${POSTGRES_USER}
30 POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
31 POSTGRES_DB: gitea
32 volumes:
33 - gitea_postgres:/var/lib/postgresql/data
34 healthcheck:
35 test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
36 interval: 10s
37 timeout: 5s
38 retries: 5
39 networks:
40 - drone-net
41 restart: unless-stopped
42
43 drone:
44 image: drone/drone:latest
45 environment:
46 DRONE_GITEA_SERVER: http://gitea:3000
47 DRONE_GITEA_CLIENT_ID: ${DRONE_GITEA_CLIENT_ID}
48 DRONE_GITEA_CLIENT_SECRET: ${DRONE_GITEA_CLIENT_SECRET}
49 DRONE_RPC_SECRET: ${DRONE_RPC_SECRET}
50 DRONE_SERVER_HOST: ${DRONE_SERVER_HOST}
51 DRONE_SERVER_PROTO: http
52 DRONE_DATABASE_DRIVER: postgres
53 DRONE_DATABASE_DATASOURCE: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@drone-db:5432/drone?sslmode=disable
54 ports:
55 - "8080:80"
56 volumes:
57 - drone_data:/data
58 depends_on:
59 drone-db:
60 condition: service_healthy
61 gitea:
62 condition: service_started
63 networks:
64 - drone-net
65 restart: unless-stopped
66
67 drone-db:
68 image: postgres:15-alpine
69 environment:
70 POSTGRES_USER: ${POSTGRES_USER}
71 POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
72 POSTGRES_DB: drone
73 volumes:
74 - drone_postgres:/var/lib/postgresql/data
75 healthcheck:
76 test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
77 interval: 10s
78 timeout: 5s
79 retries: 5
80 networks:
81 - drone-net
82 restart: unless-stopped
83
84 drone-runner:
85 image: drone/drone-runner-docker:latest
86 environment:
87 DRONE_RPC_PROTO: http
88 DRONE_RPC_HOST: drone
89 DRONE_RPC_SECRET: ${DRONE_RPC_SECRET}
90 DRONE_RUNNER_CAPACITY: 2
91 DRONE_RUNNER_NAME: docker-runner
92 volumes:
93 - /var/run/docker.sock:/var/run/docker.sock
94 depends_on:
95 - drone
96 networks:
97 - drone-net
98 restart: unless-stopped
99
100volumes:
101 gitea_data:
102 gitea_postgres:
103 drone_data:
104 drone_postgres:
105
106networks:
107 drone-net:
108 driver: bridge
109EOF
110
111# 2. Create the .env file
112cat > .env << 'EOF'
113# PostgreSQL (shared)
114POSTGRES_USER=drone
115POSTGRES_PASSWORD=secure_postgres_password
116
117# Drone Server
118DRONE_SERVER_HOST=localhost:8080
119DRONE_RPC_SECRET=your_rpc_secret_here
120
121# Gitea OAuth2 (create in Gitea Settings > Applications)
122DRONE_GITEA_CLIENT_ID=your_client_id
123DRONE_GITEA_CLIENT_SECRET=your_client_secret
124EOF
125
126# 3. Start the services
127docker compose up -d
128
129# 4. View logs
130docker compose logs -f

[>]One-Liner

Run this command to download and set up the recipe in one step:

[terminal]
1curl -fsSL https://docker.recipes/api/recipes/drone-ci-stack/run | bash

[?]Troubleshooting

  • [!]OAuth2 application not found error: Create OAuth2 application in Gitea admin panel with correct redirect URI http://your-drone-host/login
  • [!]Pipeline fails with 'Cannot connect to Docker daemon': Ensure drone-runner container has access to /var/run/docker.sock and Docker socket permissions
  • [!]Gitea webhooks failing with connection refused: Verify DRONE_SERVER_HOST matches accessible hostname from Gitea container perspective
  • [!]Database connection errors on startup: Check PostgreSQL containers are healthy and POSTGRES_USER/POSTGRES_PASSWORD environment variables match across services
  • [!]Build secrets not accessible in pipeline: Verify secrets are added in Drone web UI at repository level and referenced correctly in .drone.yml with from_secret syntax
  • [!]Runner capacity exceeded errors: Increase DRONE_RUNNER_CAPACITY value or deploy additional drone-runner instances with unique DRONE_RUNNER_NAME values

Community Notes

Loading...
Loading notes...

## Download Recipe Kit

Get all files in a ready-to-deploy package

Includes docker-compose.yml, .env template, README, and license