docker.recipes

Gitea with Actions Runner

intermediate

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:latest
4 environment:
5 USER_UID: 1000
6 USER_GID: 1000
7 GITEA__database__DB_TYPE: postgres
8 GITEA__database__HOST: db:5432
9 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:/data
18 - /etc/timezone:/etc/timezone:ro
19 - /etc/localtime:/etc/localtime:ro
20 depends_on:
21 db:
22 condition: service_healthy
23 networks:
24 - gitea-net
25 restart: unless-stopped
26
27 db:
28 image: postgres:15-alpine
29 environment:
30 POSTGRES_USER: ${POSTGRES_USER}
31 POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
32 POSTGRES_DB: ${POSTGRES_DB}
33 volumes:
34 - postgres_data:/var/lib/postgresql/data
35 healthcheck:
36 test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
37 interval: 10s
38 timeout: 5s
39 retries: 5
40 networks:
41 - gitea-net
42 restart: unless-stopped
43
44 runner:
45 image: gitea/act_runner:latest
46 environment:
47 CONFIG_FILE: /config.yaml
48 GITEA_INSTANCE_URL: http://gitea:3000
49 GITEA_RUNNER_REGISTRATION_TOKEN: ${RUNNER_TOKEN}
50 GITEA_RUNNER_NAME: default-runner
51 volumes:
52 - runner_data:/data
53 - /var/run/docker.sock:/var/run/docker.sock
54 - ./config.yaml:/config.yaml:ro
55 depends_on:
56 - gitea
57 networks:
58 - gitea-net
59 restart: unless-stopped
60
61 nginx:
62 image: nginx:alpine
63 ports:
64 - "80:80"
65 - "443:443"
66 volumes:
67 - ./nginx.conf:/etc/nginx/nginx.conf:ro
68 - ./certs:/etc/nginx/certs:ro
69 depends_on:
70 - gitea
71 networks:
72 - gitea-net
73 restart: unless-stopped
74
75volumes:
76 gitea_data:
77 postgres_data:
78 runner_data:
79
80networks:
81 gitea-net:
82 driver: bridge

.env Template

.env
1# PostgreSQL
2POSTGRES_USER=gitea
3POSTGRES_PASSWORD=secure_postgres_password
4POSTGRES_DB=gitea
5
6# Runner Registration Token
7# Get from Gitea Admin > Actions > Runners
8RUNNER_TOKEN=your_runner_token_here

Usage Notes

  1. 1Gitea at http://localhost:3000
  2. 2SSH clone via port 222
  3. 3Enable Actions in repo settings
  4. 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 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: db:5432
11 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:/data
20 - /etc/timezone:/etc/timezone:ro
21 - /etc/localtime:/etc/localtime:ro
22 depends_on:
23 db:
24 condition: service_healthy
25 networks:
26 - gitea-net
27 restart: unless-stopped
28
29 db:
30 image: postgres:15-alpine
31 environment:
32 POSTGRES_USER: ${POSTGRES_USER}
33 POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
34 POSTGRES_DB: ${POSTGRES_DB}
35 volumes:
36 - postgres_data:/var/lib/postgresql/data
37 healthcheck:
38 test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
39 interval: 10s
40 timeout: 5s
41 retries: 5
42 networks:
43 - gitea-net
44 restart: unless-stopped
45
46 runner:
47 image: gitea/act_runner:latest
48 environment:
49 CONFIG_FILE: /config.yaml
50 GITEA_INSTANCE_URL: http://gitea:3000
51 GITEA_RUNNER_REGISTRATION_TOKEN: ${RUNNER_TOKEN}
52 GITEA_RUNNER_NAME: default-runner
53 volumes:
54 - runner_data:/data
55 - /var/run/docker.sock:/var/run/docker.sock
56 - ./config.yaml:/config.yaml:ro
57 depends_on:
58 - gitea
59 networks:
60 - gitea-net
61 restart: unless-stopped
62
63 nginx:
64 image: nginx:alpine
65 ports:
66 - "80:80"
67 - "443:443"
68 volumes:
69 - ./nginx.conf:/etc/nginx/nginx.conf:ro
70 - ./certs:/etc/nginx/certs:ro
71 depends_on:
72 - gitea
73 networks:
74 - gitea-net
75 restart: unless-stopped
76
77volumes:
78 gitea_data:
79 postgres_data:
80 runner_data:
81
82networks:
83 gitea-net:
84 driver: bridge
85EOF
86
87# 2. Create the .env file
88cat > .env << 'EOF'
89# PostgreSQL
90POSTGRES_USER=gitea
91POSTGRES_PASSWORD=secure_postgres_password
92POSTGRES_DB=gitea
93
94# Runner Registration Token
95# Get from Gitea Admin > Actions > Runners
96RUNNER_TOKEN=your_runner_token_here
97EOF
98
99# 3. Start the services
100docker compose up -d
101
102# 4. View logs
103docker 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/gitea-ci-stack/run | bash

Troubleshooting

  • 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

Ad Space