NEWS Earn Money with Onidel Cloud! Affiliate Program Details - Check it out

How to Deploy a Production‑Ready Gitea on an Ubuntu 24.04 VPS with Docker Compose, PostgreSQL, MinIO/S3 for LFS/Artifacts, OIDC SSO, Actions Runners, and TLS (2025 Tutorial)

Introduction

Gitea is a lightweight, self-hosted Git service with features comparable to GitHub, including Git LFS, CI/CD actions, and package registries. This tutorial shows you how to deploy a production-ready Gitea instance on an Onidel VPS in Singapore with enterprise-grade features including PostgreSQL database, MinIO for artifacts and LFS storage, OIDC single sign-on, and dedicated Actions runners.

By the end of this guide, you’ll have a scalable Git platform capable of handling large repositories with LFS support, automated CI/CD workflows, and secure authentication through your existing identity provider.

Prerequisites

Before starting, ensure you have:

  • Ubuntu 24.04 LTS VPS with at least 4GB RAM and 40GB storage
  • Docker and Docker Compose v2.20+ installed
  • Domain name pointing to your server’s IP address
  • OIDC provider (optional) for SSO authentication
  • S3-compatible storage credentials (for external object storage)

Resource Requirements:

  • CPU: 2+ cores (4+ recommended for Actions)
  • RAM: 4GB minimum (8GB+ for heavy CI/CD workloads)
  • Storage: 40GB+ with fast NVMe for optimal Git operations

Step-by-Step Tutorial

Step 1: Prepare Directory Structure

Create the deployment directory and generate necessary secrets:

mkdir -p ~/gitea-production/{data,postgres,minio,config}
cd ~/gitea-production

# Generate secure passwords
echo "POSTGRES_PASSWORD=$(openssl rand -base64 32)" > .env
echo "MINIO_ROOT_PASSWORD=$(openssl rand -base64 24)" >> .env
echo "GITEA_SECRET_KEY=$(openssl rand -base64 32)" >> .env
echo "INTERNAL_TOKEN=$(openssl rand -base64 32)" >> .env

Step 2: Create Docker Compose Configuration

Create the main docker-compose.yml file with all services:

version: '3.8'

services:
  postgres:
    image: postgres:16-alpine
    container_name: gitea-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: gitea
      POSTGRES_USER: gitea
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./postgres:/var/lib/postgresql/data
    networks:
      - gitea-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U gitea"]
      interval: 10s
      timeout: 5s
      retries: 5

  minio:
    image: minio/minio:RELEASE.2024-12-13T22-19-12Z
    container_name: gitea-minio
    restart: unless-stopped
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: gitea
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
    volumes:
      - ./minio:/data
    networks:
      - gitea-network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3

  gitea:
    image: gitea/gitea:1.22-dev
    container_name: gitea-server
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
      minio:
        condition: service_healthy
    environment:
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=postgres:5432
      - GITEA__database__NAME=gitea
      - GITEA__database__USER=gitea
      - GITEA__database__PASSWD=${POSTGRES_PASSWORD}
      - GITEA__server__DOMAIN=git.yourdomain.com
      - GITEA__server__SSH_DOMAIN=git.yourdomain.com
      - GITEA__server__ROOT_URL=https://git.yourdomain.com
      - GITEA__security__SECRET_KEY=${GITEA_SECRET_KEY}
      - GITEA__security__INTERNAL_TOKEN=${INTERNAL_TOKEN}
      - GITEA__storage__STORAGE_TYPE=minio
      - GITEA__storage__MINIO_ENDPOINT=minio:9000
      - GITEA__storage__MINIO_ACCESS_KEY_ID=gitea
      - GITEA__storage__MINIO_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}
      - GITEA__storage__MINIO_BUCKET=gitea
      - GITEA__storage__MINIO_USE_SSL=false
      - GITEA__lfs__STORAGE_TYPE=minio
      - GITEA__packages__STORAGE_TYPE=minio
      - GITEA__actions__ENABLED=true
    volumes:
      - ./data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "2222:22"
    networks:
      - gitea-network

  gitea-runner:
    image: gitea/act_runner:nightly
    container_name: gitea-runner
    restart: unless-stopped
    depends_on:
      - gitea
    environment:
      GITEA_INSTANCE_URL: http://gitea:3000
      GITEA_RUNNER_REGISTRATION_TOKEN: ${RUNNER_TOKEN}
    volumes:
      - ./runner-data:/data
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - gitea-network

  caddy:
    image: caddy:2.7-alpine
    container_name: gitea-caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./caddy-data:/data
      - ./caddy-config:/config
    networks:
      - gitea-network

networks:
  gitea-network:
    driver: bridge

volumes:
  postgres-data:
  minio-data:
  gitea-data:
  caddy-data:
  caddy-config:

Step 3: Configure Reverse Proxy with TLS

Create a Caddyfile for automatic TLS certificate provisioning:

git.yourdomain.com {
    reverse_proxy gitea:3000
    
    # Security headers
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        Referrer-Policy "strict-origin-when-cross-origin"
    }
    
    # Enable HTTP/3
    protocols h1 h2 h3
}

# MinIO admin console (optional)
minio-admin.yourdomain.com {
    reverse_proxy minio:9001
}

Step 4: Initialize MinIO Buckets

Start MinIO first to create the required buckets:

# Start only MinIO initially
docker-compose up -d minio

# Install MinIO client
wget https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc && sudo mv mc /usr/local/bin/

# Configure MinIO client
mc alias set gitea-minio http://localhost:9000 gitea $(grep MINIO_ROOT_PASSWORD .env | cut -d'=' -f2)

# Create required buckets
mc mb gitea-minio/gitea
mc mb gitea-minio/gitea-lfs
mc mb gitea-minio/gitea-packages

Step 5: Deploy Complete Stack

Start all services and verify deployment:

# Deploy the complete stack
docker-compose up -d

# Monitor logs for any issues
docker-compose logs -f gitea

# Check service health
docker-compose ps

Warning: Initial startup may take 2-3 minutes as Gitea initializes the database schema and connects to object storage.

Step 6: Configure OIDC SSO

Navigate to your Gitea admin panel at https://git.yourdomain.com and configure OIDC authentication:

  1. Go to Site AdministrationAuthentication Sources
  2. Add new OAuth2 provider with these settings:
    • Provider: OpenID Connect
    • Client ID: Your OIDC client ID
    • Client Secret: Your OIDC client secret
    • Discovery URL: https://your-oidc-provider/.well-known/openid-configuration

Step 7: Register Actions Runner

Register the Actions runner with your Gitea instance:

# Get runner registration token from Gitea admin panel
# Add it to your .env file
echo "RUNNER_TOKEN=your_registration_token_here" >> .env

# Restart the runner service
docker-compose restart gitea-runner

# Verify runner registration
docker-compose logs gitea-runner

Best Practices

Security Hardening

  • Enable 2FA: Force two-factor authentication for all administrators
  • Restrict SSH access: Use non-standard port 2222 and implement key-based authentication only
  • Network isolation: Keep database and object storage on internal network
  • Regular updates: Implement automated security updates for the host system

Backup Strategy

Implement comprehensive backups for all data layers:

# Backup PostgreSQL database
docker-compose exec postgres pg_dump -U gitea gitea > backup-$(date +%Y%m%d).sql

# Backup Git repositories
tar -czf repos-backup-$(date +%Y%m%d).tar.gz ./data/git/repositories/

# Sync MinIO data to external S3
mc mirror gitea-minio/gitea s3/your-backup-bucket/gitea-$(date +%Y%m%d)/

Consider implementing automated backups using encrypted VPS backup solutions for comprehensive protection.

Performance Optimization

  • Database tuning: Configure PostgreSQL shared_buffers and work_mem for your workload
  • Git LFS caching: Enable MinIO caching for frequently accessed LFS objects
  • Actions concurrency: Scale runner instances based on CI/CD demand
  • Monitoring: Implement observability stack for performance insights

Scalability Considerations

For high-traffic environments:

Conclusion

You now have a production-ready Gitea deployment with enterprise features including PostgreSQL persistence, S3-compatible LFS storage, OIDC authentication, and automated CI/CD capabilities. This setup provides a solid foundation for teams requiring self-hosted Git services with GitHub-like functionality.

The containerized architecture ensures easy maintenance and scaling, while the comprehensive backup strategy protects your critical source code assets. For enhanced security and global accessibility, consider deploying this stack on an Onidel VPS in Singapore with our high-performance EPYC processors and enterprise-grade NVMe storage.

We encourage you to explore Onidel’s VPS offerings in Singapore, Sydney, and Amsterdam for hosting your critical development infrastructure with features like automatic backups, private networks, and advanced security options including AMD-SEV encryption.

Share your love