Gitea with Actions Runner
Self-hosted Git with Gitea, built-in Actions CI/CD runner, and PostgreSQL backend.
Overview
Gitea is a lightweight, self-hosted Git service written in Go that provides GitHub-like functionality with minimal resource requirements. Born as a fork of Gogs in 2016, Gitea has evolved into a comprehensive Git platform offering repository hosting, pull requests, issue tracking, and built-in CI/CD capabilities through Gitea Actions. Its design philosophy emphasizes simplicity, performance, and ease of deployment, making it an excellent alternative to heavier solutions like GitLab for teams that need essential Git hosting features without the complexity.
This deployment creates a complete self-hosted Git platform with CI/CD capabilities using four interconnected services: the main Gitea application server, a PostgreSQL database for robust data storage, a dedicated Actions runner for executing CI/CD pipelines, and an Nginx reverse proxy for web traffic management. The PostgreSQL backend provides enterprise-grade reliability and performance compared to SQLite, while the integrated runner enables automated testing, building, and deployment workflows directly within your repositories. The Nginx frontend handles SSL termination, load balancing, and can serve static assets efficiently.
This stack is ideal for development teams, organizations, and individuals who need complete control over their Git infrastructure while maintaining the familiar GitHub-style workflow. The combination of Gitea's lightweight design with PostgreSQL's reliability makes it suitable for everything from personal projects to small-to-medium enterprise deployments, especially where data sovereignty, customization, and cost control are priorities.
Key Features
- GitHub-compatible Actions CI/CD with dedicated runner container supporting Docker-based workflows
- PostgreSQL backend providing ACID compliance and advanced query capabilities for repository metadata
- Built-in pull request system with code review, diff viewing, and merge conflict resolution
- Issue tracking with milestones, labels, assignees, and project boards for agile development
- OAuth2 and LDAP authentication support for enterprise identity integration
- Repository mirroring and migration tools for GitHub, GitLab, and other Git platforms
- Webhook system for integrating with external services and custom automation
- Nginx reverse proxy with SSL/TLS termination and static file caching optimization
Common Use Cases
- 1Private Git hosting for proprietary codebases requiring data sovereignty and security control
- 2CI/CD automation for small to medium development teams with Docker-based build pipelines
- 3Code review workflows for distributed teams needing GitHub-like collaboration features
- 4Migration target for teams moving away from cloud Git providers to reduce costs
- 5Educational environments teaching Git workflows and DevOps practices with full infrastructure control
- 6Open source project hosting with integrated issue tracking and community contribution management
- 7Enterprise development environments requiring LDAP integration and custom authentication policies
Prerequisites
- Docker and Docker Compose with at least 2GB RAM available for the complete stack
- Ports 80, 443, 3000, and 222 available on the host system for web and SSH access
- Valid SSL certificates placed in ./certs/ directory for HTTPS functionality
- Runner registration token generated from Gitea admin panel after initial setup
- Basic understanding of Git workflows, Docker containers, and CI/CD pipeline concepts
- Nginx configuration file (./nginx.conf) customized for your domain and SSL setup
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 environment: 5 USER_UID: 10006 USER_GID: 10007 GITEA__database__DB_TYPE: postgres8 GITEA__database__HOST: db:54329 GITEA__database__NAME: ${POSTGRES_DB}10 GITEA__database__USER: ${POSTGRES_USER}11 GITEA__database__PASSWD: ${POSTGRES_PASSWORD}12 GITEA__actions__ENABLED: "true"13 ports: 14 - "3000:3000"15 - "222:22"16 volumes: 17 - gitea_data:/data18 - /etc/timezone:/etc/timezone:ro19 - /etc/localtime:/etc/localtime:ro20 depends_on: 21 db: 22 condition: service_healthy23 networks: 24 - gitea-net25 restart: unless-stopped2627 db: 28 image: postgres:15-alpine29 environment: 30 POSTGRES_USER: ${POSTGRES_USER}31 POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}32 POSTGRES_DB: ${POSTGRES_DB}33 volumes: 34 - postgres_data:/var/lib/postgresql/data35 healthcheck: 36 test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]37 interval: 10s38 timeout: 5s39 retries: 540 networks: 41 - gitea-net42 restart: unless-stopped4344 runner: 45 image: gitea/act_runner:latest46 environment: 47 CONFIG_FILE: /config.yaml48 GITEA_INSTANCE_URL: http://gitea:300049 GITEA_RUNNER_REGISTRATION_TOKEN: ${RUNNER_TOKEN}50 GITEA_RUNNER_NAME: default-runner51 volumes: 52 - runner_data:/data53 - /var/run/docker.sock:/var/run/docker.sock54 - ./config.yaml:/config.yaml:ro55 depends_on: 56 - gitea57 networks: 58 - gitea-net59 restart: unless-stopped6061 nginx: 62 image: nginx:alpine63 ports: 64 - "80:80"65 - "443:443"66 volumes: 67 - ./nginx.conf:/etc/nginx/nginx.conf:ro68 - ./certs:/etc/nginx/certs:ro69 depends_on: 70 - gitea71 networks: 72 - gitea-net73 restart: unless-stopped7475volumes: 76 gitea_data: 77 postgres_data: 78 runner_data: 7980networks: 81 gitea-net: 82 driver: bridge.env Template
.env
1# PostgreSQL2POSTGRES_USER=gitea3POSTGRES_PASSWORD=secure_postgres_password4POSTGRES_DB=gitea56# Runner Registration Token7# Get from Gitea Admin > Actions > Runners8RUNNER_TOKEN=your_runner_token_hereUsage Notes
- 1Gitea at http://localhost:3000
- 2SSH clone via port 222
- 3Enable Actions in repo settings
- 4Create runner token in admin panel
Individual Services(4 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: db:5432
GITEA__database__NAME: ${POSTGRES_DB}
GITEA__database__USER: ${POSTGRES_USER}
GITEA__database__PASSWD: ${POSTGRES_PASSWORD}
GITEA__actions__ENABLED: "true"
ports:
- "3000:3000"
- "222:22"
volumes:
- gitea_data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
depends_on:
db:
condition: service_healthy
networks:
- gitea-net
restart: unless-stopped
db
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test:
- CMD-SHELL
- pg_isready -U ${POSTGRES_USER}
interval: 10s
timeout: 5s
retries: 5
networks:
- gitea-net
restart: unless-stopped
runner
runner:
image: gitea/act_runner:latest
environment:
CONFIG_FILE: /config.yaml
GITEA_INSTANCE_URL: http://gitea:3000
GITEA_RUNNER_REGISTRATION_TOKEN: ${RUNNER_TOKEN}
GITEA_RUNNER_NAME: default-runner
volumes:
- runner_data:/data
- /var/run/docker.sock:/var/run/docker.sock
- ./config.yaml:/config.yaml:ro
depends_on:
- gitea
networks:
- gitea-net
restart: unless-stopped
nginx
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- gitea
networks:
- gitea-net
restart: unless-stopped
Quick Start
terminal
1# 1. Create the compose file2cat > docker-compose.yml << 'EOF'3services:4 gitea:5 image: gitea/gitea:latest6 environment:7 USER_UID: 10008 USER_GID: 10009 GITEA__database__DB_TYPE: postgres10 GITEA__database__HOST: db:543211 GITEA__database__NAME: ${POSTGRES_DB}12 GITEA__database__USER: ${POSTGRES_USER}13 GITEA__database__PASSWD: ${POSTGRES_PASSWORD}14 GITEA__actions__ENABLED: "true"15 ports:16 - "3000:3000"17 - "222:22"18 volumes:19 - gitea_data:/data20 - /etc/timezone:/etc/timezone:ro21 - /etc/localtime:/etc/localtime:ro22 depends_on:23 db:24 condition: service_healthy25 networks:26 - gitea-net27 restart: unless-stopped2829 db:30 image: postgres:15-alpine31 environment:32 POSTGRES_USER: ${POSTGRES_USER}33 POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}34 POSTGRES_DB: ${POSTGRES_DB}35 volumes:36 - postgres_data:/var/lib/postgresql/data37 healthcheck:38 test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]39 interval: 10s40 timeout: 5s41 retries: 542 networks:43 - gitea-net44 restart: unless-stopped4546 runner:47 image: gitea/act_runner:latest48 environment:49 CONFIG_FILE: /config.yaml50 GITEA_INSTANCE_URL: http://gitea:300051 GITEA_RUNNER_REGISTRATION_TOKEN: ${RUNNER_TOKEN}52 GITEA_RUNNER_NAME: default-runner53 volumes:54 - runner_data:/data55 - /var/run/docker.sock:/var/run/docker.sock56 - ./config.yaml:/config.yaml:ro57 depends_on:58 - gitea59 networks:60 - gitea-net61 restart: unless-stopped6263 nginx:64 image: nginx:alpine65 ports:66 - "80:80"67 - "443:443"68 volumes:69 - ./nginx.conf:/etc/nginx/nginx.conf:ro70 - ./certs:/etc/nginx/certs:ro71 depends_on:72 - gitea73 networks:74 - gitea-net75 restart: unless-stopped7677volumes:78 gitea_data:79 postgres_data:80 runner_data:8182networks:83 gitea-net:84 driver: bridge85EOF8687# 2. Create the .env file88cat > .env << 'EOF'89# PostgreSQL90POSTGRES_USER=gitea91POSTGRES_PASSWORD=secure_postgres_password92POSTGRES_DB=gitea9394# Runner Registration Token95# Get from Gitea Admin > Actions > Runners96RUNNER_TOKEN=your_runner_token_here97EOF9899# 3. Start the services100docker compose up -d101102# 4. View logs103docker 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-ci-stack/run | bashTroubleshooting
- Runner container failing to register: Verify GITEA_RUNNER_REGISTRATION_TOKEN matches the token from Gitea admin settings and that the gitea service is fully started
- Database connection errors on startup: Ensure PostgreSQL environment variables match between gitea and db services, and wait for the health check to pass before Gitea starts
- SSH clone operations failing on port 222: Check that the port is not blocked by firewall and that SSH_PORT is correctly configured in Gitea settings
- Actions workflows stuck in pending: Verify /var/run/docker.sock is properly mounted in runner container and Docker daemon is accessible
- Nginx returning 502 Bad Gateway: Confirm gitea service is running and accessible on port 3000, and check nginx.conf upstream configuration
- PostgreSQL data corruption after restart: Ensure postgres_data volume has proper permissions and sufficient disk space for database operations
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
giteagitea-runnerpostgresqlnginx
Tags
#gitea#git#ci-cd#actions#self-hosted
Category
DevOps & CI/CDAd Space
Shortcuts: C CopyF FavoriteD Download