Gitea + Drone CI + Registry
Complete self-hosted Git platform with CI/CD pipeline and container registry.
Overview
Gitea is a lightweight, self-hosted Git service written in Go that provides GitHub-like functionality with minimal resource requirements. Originally forked from Gogs in 2016, Gitea has evolved into a full-featured Git platform offering repository hosting, issue tracking, pull requests, and built-in package registry support. Its philosophy centers on simplicity and performance, making it ideal for teams who want GitHub's features without the complexity of GitLab or dependency on external services.
This stack combines Gitea with Drone CI and a Docker Registry to create a complete DevOps pipeline. Drone CI acts as the continuous integration engine, automatically building and testing code when developers push to Gitea repositories. The Docker Registry stores the resulting container images, while Drone runners execute builds in isolated Docker containers. This integration creates an automated workflow where code commits trigger builds, tests run in consistent environments, and successful builds produce deployable artifacts stored in your private registry.
Development teams, DevOps engineers, and organizations prioritizing data sovereignty will find this stack particularly valuable. Unlike cloud-based solutions, this combination runs entirely on your infrastructure, ensuring code privacy and eliminating vendor lock-in. Small to medium teams benefit from Gitea's lightweight nature (requiring only 512MB RAM), while the Drone CI integration provides enterprise-grade automation without the licensing costs of Jenkins or GitLab CI. The included Docker Registry completes the picture by providing secure, private container image storage with configurable retention policies.
Key Features
- GitHub-like web interface with pull requests, code review, and issue tracking
- PostgreSQL backend for both Gitea and Drone ensuring data consistency and reliability
- Drone CI pipeline automation with YAML-based configuration and Docker container execution
- Docker Registry v2 API compliance with image deletion and garbage collection support
- OAuth2 integration between Gitea and Drone for single sign-on authentication
- Multi-architecture container support through Drone's Docker runner capabilities
- Webhook-triggered builds automatically starting CI pipelines on Git push events
- Built-in package registry support in Gitea for npm, Docker, and Maven artifacts
Common Use Cases
- 1Private software development teams needing GitHub-like collaboration without SaaS dependencies
- 2Organizations with compliance requirements mandating on-premises code hosting and CI/CD
- 3Startups building containerized applications requiring integrated Git hosting and image registry
- 4Development teams migrating from GitHub/GitLab seeking cost-effective self-hosted alternatives
- 5Educational institutions teaching DevOps practices with complete pipeline visibility
- 6Open source projects requiring private development repositories with public release automation
- 7Consultancies managing multiple client codebases with isolated, branded Git platforms
Prerequisites
- Docker host with minimum 2GB RAM (1GB for services, 1GB for build processes)
- Available ports 3000 (Gitea web), 222 (SSH), 8080 (Drone), and 5000 (Registry)
- Domain names or hostnames configured for Gitea and Drone with SSL certificates recommended
- Basic understanding of OAuth2 application setup for Gitea-Drone integration
- Familiarity with YAML syntax for creating Drone CI pipeline configurations
- PostgreSQL administration knowledge for database maintenance and backups
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:latest4 container_name: gitea5 environment: 6 - USER_UID=10007 - USER_GID=10008 - GITEA__database__DB_TYPE=postgres9 - GITEA__database__HOST=gitea-db:543210 - GITEA__database__NAME=gitea11 - GITEA__database__USER=gitea12 - GITEA__database__PASSWD=${DB_PASSWORD}13 - GITEA__server__ROOT_URL=${GITEA_URL}14 - GITEA__server__SSH_DOMAIN=${GITEA_DOMAIN}15 - GITEA__server__SSH_PORT=22216 volumes: 17 - gitea-data:/data18 - /etc/timezone:/etc/timezone:ro19 - /etc/localtime:/etc/localtime:ro20 ports: 21 - "3000:3000"22 - "222:22"23 depends_on: 24 - gitea-db25 networks: 26 - gitea-network27 restart: unless-stopped2829 gitea-db: 30 image: postgres:15-alpine31 container_name: gitea-db32 environment: 33 - POSTGRES_USER=gitea34 - POSTGRES_PASSWORD=${DB_PASSWORD}35 - POSTGRES_DB=gitea36 volumes: 37 - gitea-postgres:/var/lib/postgresql/data38 networks: 39 - gitea-network40 restart: unless-stopped4142 drone: 43 image: drone/drone:244 container_name: drone45 environment: 46 - DRONE_GITEA_SERVER=${GITEA_URL}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_HOST}51 - DRONE_SERVER_PROTO=https52 - DRONE_DATABASE_DRIVER=postgres53 - DRONE_DATABASE_DATASOURCE=postgres://drone:${DRONE_DB_PASSWORD}@drone-db:5432/drone?sslmode=disable54 volumes: 55 - drone-data:/data56 ports: 57 - "8080:80"58 depends_on: 59 - drone-db60 networks: 61 - gitea-network62 restart: unless-stopped6364 drone-db: 65 image: postgres:15-alpine66 container_name: drone-db67 environment: 68 - POSTGRES_USER=drone69 - POSTGRES_PASSWORD=${DRONE_DB_PASSWORD}70 - POSTGRES_DB=drone71 volumes: 72 - drone-postgres:/var/lib/postgresql/data73 networks: 74 - gitea-network75 restart: unless-stopped7677 drone-runner: 78 image: drone/drone-runner-docker:179 container_name: drone-runner80 environment: 81 - DRONE_RPC_PROTO=http82 - DRONE_RPC_HOST=drone83 - DRONE_RPC_SECRET=${DRONE_RPC_SECRET}84 - DRONE_RUNNER_CAPACITY=285 - DRONE_RUNNER_NAME=docker-runner86 volumes: 87 - /var/run/docker.sock:/var/run/docker.sock88 depends_on: 89 - drone90 networks: 91 - gitea-network92 restart: unless-stopped9394 registry: 95 image: registry:296 container_name: registry97 environment: 98 - REGISTRY_HTTP_SECRET=${REGISTRY_SECRET}99 - REGISTRY_STORAGE_DELETE_ENABLED=true100 volumes: 101 - registry-data:/var/lib/registry102 ports: 103 - "5000:5000"104 networks: 105 - gitea-network106 restart: unless-stopped107108volumes: 109 gitea-data: 110 gitea-postgres: 111 drone-data: 112 drone-postgres: 113 registry-data: 114115networks: 116 gitea-network: 117 driver: bridge.env Template
.env
1# Gitea + Drone CI + Registry2DB_PASSWORD=secure_gitea_password3GITEA_URL=http://localhost:30004GITEA_DOMAIN=localhost56# Drone CI7DRONE_GITEA_CLIENT_ID=your_oauth_client_id8DRONE_GITEA_CLIENT_SECRET=your_oauth_client_secret9DRONE_RPC_SECRET=super_secret_rpc_key10DRONE_HOST=localhost:808011DRONE_DB_PASSWORD=secure_drone_password1213# Registry14REGISTRY_SECRET=registry_http_secretUsage Notes
- 1Gitea at http://localhost:3000
- 2Drone CI at http://localhost:8080
- 3Registry at http://localhost:5000
- 4Create OAuth app in Gitea for Drone
- 5Configure webhooks for CI triggers
Individual Services(6 services)
Copy individual services to mix and match with your existing compose files.
gitea
gitea:
image: gitea/gitea:latest
container_name: gitea
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=gitea
- GITEA__database__PASSWD=${DB_PASSWORD}
- GITEA__server__ROOT_URL=${GITEA_URL}
- GITEA__server__SSH_DOMAIN=${GITEA_DOMAIN}
- GITEA__server__SSH_PORT=222
volumes:
- gitea-data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "222:22"
depends_on:
- gitea-db
networks:
- gitea-network
restart: unless-stopped
gitea-db
gitea-db:
image: postgres:15-alpine
container_name: gitea-db
environment:
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=gitea
volumes:
- gitea-postgres:/var/lib/postgresql/data
networks:
- gitea-network
restart: unless-stopped
drone
drone:
image: drone/drone:2
container_name: drone
environment:
- DRONE_GITEA_SERVER=${GITEA_URL}
- 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_HOST}
- DRONE_SERVER_PROTO=https
- DRONE_DATABASE_DRIVER=postgres
- DRONE_DATABASE_DATASOURCE=postgres://drone:${DRONE_DB_PASSWORD}@drone-db:5432/drone?sslmode=disable
volumes:
- drone-data:/data
ports:
- "8080:80"
depends_on:
- drone-db
networks:
- gitea-network
restart: unless-stopped
drone-db
drone-db:
image: postgres:15-alpine
container_name: drone-db
environment:
- POSTGRES_USER=drone
- POSTGRES_PASSWORD=${DRONE_DB_PASSWORD}
- POSTGRES_DB=drone
volumes:
- drone-postgres:/var/lib/postgresql/data
networks:
- gitea-network
restart: unless-stopped
drone-runner
drone-runner:
image: drone/drone-runner-docker:1
container_name: drone-runner
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:
- gitea-network
restart: unless-stopped
registry
registry:
image: registry:2
container_name: registry
environment:
- REGISTRY_HTTP_SECRET=${REGISTRY_SECRET}
- REGISTRY_STORAGE_DELETE_ENABLED=true
volumes:
- registry-data:/var/lib/registry
ports:
- "5000:5000"
networks:
- gitea-network
restart: unless-stopped
Quick Start
terminal
1# 1. Create the compose file2cat > docker-compose.yml << 'EOF'3services:4 gitea:5 image: gitea/gitea:latest6 container_name: gitea7 environment:8 - USER_UID=10009 - USER_GID=100010 - GITEA__database__DB_TYPE=postgres11 - GITEA__database__HOST=gitea-db:543212 - GITEA__database__NAME=gitea13 - GITEA__database__USER=gitea14 - GITEA__database__PASSWD=${DB_PASSWORD}15 - GITEA__server__ROOT_URL=${GITEA_URL}16 - GITEA__server__SSH_DOMAIN=${GITEA_DOMAIN}17 - GITEA__server__SSH_PORT=22218 volumes:19 - gitea-data:/data20 - /etc/timezone:/etc/timezone:ro21 - /etc/localtime:/etc/localtime:ro22 ports:23 - "3000:3000"24 - "222:22"25 depends_on:26 - gitea-db27 networks:28 - gitea-network29 restart: unless-stopped3031 gitea-db:32 image: postgres:15-alpine33 container_name: gitea-db34 environment:35 - POSTGRES_USER=gitea36 - POSTGRES_PASSWORD=${DB_PASSWORD}37 - POSTGRES_DB=gitea38 volumes:39 - gitea-postgres:/var/lib/postgresql/data40 networks:41 - gitea-network42 restart: unless-stopped4344 drone:45 image: drone/drone:246 container_name: drone47 environment:48 - DRONE_GITEA_SERVER=${GITEA_URL}49 - DRONE_GITEA_CLIENT_ID=${DRONE_GITEA_CLIENT_ID}50 - DRONE_GITEA_CLIENT_SECRET=${DRONE_GITEA_CLIENT_SECRET}51 - DRONE_RPC_SECRET=${DRONE_RPC_SECRET}52 - DRONE_SERVER_HOST=${DRONE_HOST}53 - DRONE_SERVER_PROTO=https54 - DRONE_DATABASE_DRIVER=postgres55 - DRONE_DATABASE_DATASOURCE=postgres://drone:${DRONE_DB_PASSWORD}@drone-db:5432/drone?sslmode=disable56 volumes:57 - drone-data:/data58 ports:59 - "8080:80"60 depends_on:61 - drone-db62 networks:63 - gitea-network64 restart: unless-stopped6566 drone-db:67 image: postgres:15-alpine68 container_name: drone-db69 environment:70 - POSTGRES_USER=drone71 - POSTGRES_PASSWORD=${DRONE_DB_PASSWORD}72 - POSTGRES_DB=drone73 volumes:74 - drone-postgres:/var/lib/postgresql/data75 networks:76 - gitea-network77 restart: unless-stopped7879 drone-runner:80 image: drone/drone-runner-docker:181 container_name: drone-runner82 environment:83 - DRONE_RPC_PROTO=http84 - DRONE_RPC_HOST=drone85 - DRONE_RPC_SECRET=${DRONE_RPC_SECRET}86 - DRONE_RUNNER_CAPACITY=287 - DRONE_RUNNER_NAME=docker-runner88 volumes:89 - /var/run/docker.sock:/var/run/docker.sock90 depends_on:91 - drone92 networks:93 - gitea-network94 restart: unless-stopped9596 registry:97 image: registry:298 container_name: registry99 environment:100 - REGISTRY_HTTP_SECRET=${REGISTRY_SECRET}101 - REGISTRY_STORAGE_DELETE_ENABLED=true102 volumes:103 - registry-data:/var/lib/registry104 ports:105 - "5000:5000"106 networks:107 - gitea-network108 restart: unless-stopped109110volumes:111 gitea-data:112 gitea-postgres:113 drone-data:114 drone-postgres:115 registry-data:116117networks:118 gitea-network:119 driver: bridge120EOF121122# 2. Create the .env file123cat > .env << 'EOF'124# Gitea + Drone CI + Registry125DB_PASSWORD=secure_gitea_password126GITEA_URL=http://localhost:3000127GITEA_DOMAIN=localhost128129# Drone CI130DRONE_GITEA_CLIENT_ID=your_oauth_client_id131DRONE_GITEA_CLIENT_SECRET=your_oauth_client_secret132DRONE_RPC_SECRET=super_secret_rpc_key133DRONE_HOST=localhost:8080134DRONE_DB_PASSWORD=secure_drone_password135136# Registry137REGISTRY_SECRET=registry_http_secret138EOF139140# 3. Start the services141docker compose up -d142143# 4. View logs144docker compose logs -fOne-Liner
Run this command to download and set up the recipe in one step:
terminal
1curl -fsSL https://docker.recipes/api/recipes/gitea-drone-registry/run | bashTroubleshooting
- Drone shows 'oauth2: cannot fetch token' error: Verify DRONE_GITEA_CLIENT_ID and CLIENT_SECRET match OAuth application created in Gitea settings
- Gitea SSH clone fails with permission denied: Check USER_UID/USER_GID environment variables match host user permissions on gitea-data volume
- Drone builds fail with 'Cannot connect to Docker daemon': Ensure /var/run/docker.sock is properly mounted and drone-runner has Docker socket access
- Registry push fails with 'unauthorized' error: Configure Docker daemon with insecure-registries for localhost:5000 or implement TLS certificates
- Gitea database connection errors on startup: Verify DB_PASSWORD environment variable matches between gitea and gitea-db services
- Drone runner shows 'server gave HTTP response to HTTPS client': Check DRONE_RPC_PROTO matches actual protocol used by Drone server
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
Components
giteadrone-serverdrone-runnerregistry
Tags
#git#ci-cd#registry#gitea#drone#containers
Category
DevOps & CI/CDAd Space
Shortcuts: C CopyF FavoriteD Download