DevOps

How to Deploy Gitea 1.18.0 in Docker Swarm Behind Caddy v2.6.2

Git with a cup of tea. Learn how to deploy Gitea, a painless, self-hosted, lightweight code hosting solution written in Go, to a Docker Swarm cluster.

Rajasekhar Gundala··6 min read

Gitea is a community-managed, lightweight code hosting solution written in Go. Published under the MIT license, it aims to provide the easiest, fastest, and most painless way to set up a self-hosted Git service.

In this post, I am going to show you how to deploy Gitea 1.18.0+rc1 in a Docker Swarm Cluster using Docker Compose, sitting securely behind a Caddy 2.6.2 reverse proxy.

Git with a cup of tea: A painless, self-hosted Git service.

If you want to learn more about Gitea, check out the resources below:

  1. Official Website
  2. Documentation
  3. GitHub Repository

Let’s dive into the deployment.

Prerequisites

Please ensure you fulfill the following requirements before proceeding:

  1. A Docker Swarm Cluster configured with GlusterFS for persistent storage.
  2. Caddy deployed as the ingress reverse proxy to expose microservices externally.

Introduction to Gitea

Gitea is a robust alternative to GitHub, GitLab, and Bitbucket. Originally a fork of Gogs, the project’s goal is to make self-hosting your code repositories completely effortless.

Because it is compiled with Go, it can run independently across all major platforms and architectures, including Linux, macOS, and Windows, on x86, amd64, ARM, and PowerPC.

Core Features

Gitea stands out because of its simplicity and efficiency:

  • Lightweight: Gitea has minimal system requirements. You can run it comfortably on an inexpensive Raspberry Pi, saving machine resources and energy.
  • Cross-Platform: It runs anywhere Go compiles. Choose the operating system and architecture you love, and start using it immediately.
  • Easy to Install: Simply run the binary for your platform, use the official Docker image, or install it via your OS package manager.

Persisting Gitea Data with GlusterFS

Containers deploy rapidly and use system resources efficiently. However, their filesystems are ephemeral. If a container crashes or gets rescheduled, local data is lost.

To ensure our repositories and database survive restarts and host outages, we will use GlusterFS. I previously set up a replicated GlusterFS volume to ensure data is mirrored across all nodes in the cluster.

GlusterFS Replicated Volume

The volume is mounted across all nodes. When data is written to the /mnt partition, it is instantly replicated to the other nodes in the cluster.

If any node fails, the application automatically restarts on another node without losing data.

For Gitea, we need to map the container’s /data volume to our persistent storage so that our SQLite database, configurations, and Git repositories are safely retained.

Create a folder named giteadata in the /mnt directory:

cd /mnt
sudo mkdir -p giteadata

Watch the video below for a complete guide on setting up a GlusterFS Replicated Volume.


Prepare the Deployment Environment

We will use Docker Compose to define our deployment environment.

Navigate to the /opt directory on your Swarm manager node and create the configuration directory for Gitea:

cd /opt
sudo mkdir -p gitea
cd gitea
sudo touch gitea.yml

Gitea Docker Compose Configuration

Open gitea.yml using your editor:

sudo nano gitea.yml

Paste the following Docker Compose configuration. For simplicity and low resource consumption, we are utilizing SQLite as the backend database.

version: "3.7"

services:
  gitea:
    image: gitea/gitea:latest
    volumes:
      - /mnt/giteadata:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    environment:
      - APP_NAME="Rajasekhar's Gitea"
      - RUN_MODE=prod
      - DOMAIN=gitea.example.com
      - SSH_DOMAIN=gitea.example.com
      - SSH_PORT=2222
      - SSH_LISTEN_PORT=22
      - DISABLE_SSH=false
      - HTTP_PORT=3000
      - ROOT_URL=[https://gitea.example.com](https://gitea.example.com)
      - LFS_START_SERVER=false
      - DB_TYPE=sqlite3
      - DISABLE_REGISTRATION=true
    networks:
      - caddy
    ports:
      - "3000:3000"
      - "2222:22"
    deploy:
      placement:
        constraints: [node.role == worker]
      replicas: 1
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure

volumes:
  giteadata:
    driver: "local"

networks:
  caddy:
    external: true

Watch the video below to see a walkthrough of deploying Gitea to a Docker Swarm Cluster.


Caddyfile Configuration

The Caddyfile is a highly readable configuration format for the Caddy web server.

Caddyfile is easy to write, easy to understand, and expressive enough for almost all use cases.

Here is the production-ready Caddyfile block required to expose Gitea securely. Learn more about writing Caddyfiles here.

{
    email you@example.com
    cert_issuer acme
    acme_ca [https://acme-v02.api.letsencrypt.org/directory](https://acme-v02.api.letsencrypt.org/directory)
    
    servers {
        protocol {
            experimental_http3
            allow_h2c
            strict_sni_host
        }
        timeouts {
            read_body   10s
            read_header 10s
            write       10s
            idle        2m
        }
        max_header_size 16384
    }
}

gitea.example.com {
    log {
        output file /var/log/caddy/gitea.log {
            roll_size 20mb
            roll_keep 2
            roll_keep_for 6h
        }
        format console
        level error
    }
    encode gzip zstd
    reverse_proxy gitea:3000
}

If you want more insight into deploying Caddy in a Docker Swarm cluster, check out my previous post on Caddy.

Full Stack Deployment (Combined)

If you prefer to deploy Caddy and Gitea together in a single stack, here is the combined docker-compose.yml file.

Remember to map your site data directories (like /mnt/giteadata:/data) exactly as specified.

version: "3.7"

services:
  caddy:
    image: tuneitme/caddy
    ports:
      - "80:80"
      - "443:443"
    networks:
      - caddy
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - /mnt/caddydata:/data
      - /mnt/caddyconfig:/config
      - /mnt/caddylogs:/var/log/caddy
      - /mnt/giteadata:/data
    deploy:
      placement:
        constraints:
          - node.role == manager
      replicas: 1
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure

  gitea:
    image: gitea/gitea:latest
    volumes:
      - /mnt/giteadata:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    environment:
      - APP_NAME="Rajasekhar's Gitea"
      - RUN_MODE=prod
      - DOMAIN=gitea.example.com
      - SSH_DOMAIN=gitea.example.com
      - SSH_PORT=2222
      - SSH_LISTEN_PORT=22
      - DISABLE_SSH=false
      - HTTP_PORT=3000
      - ROOT_URL=[https://gitea.example.com](https://gitea.example.com)
      - LFS_START_SERVER=false
      - DB_TYPE=sqlite3
      - DISABLE_REGISTRATION=true
    networks:
      - caddy
    ports:
      - "3000:3000"
      - "2222:22"
    deploy:
      placement:
        constraints: [node.role == worker]
      replicas: 1
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure

volumes:
  caddydata:
    driver: "local"
  caddyconfig:
    driver: "local"
  caddylogs:
    driver: "local"
  giteadata:
    driver: "local"

networks:
  caddy:
    external: true

I used a custom Caddy Docker image bundled with specific plugins (like Cloudflare DNS and Caddy Auth Portal) for my environment: Tuneit Caddy Docker Image

Deploy Gitea to Docker Swarm

Deploy the stack to your Swarm using the following command:

docker stack deploy --compose-file gitea.yml gitea

In Docker Swarm, whatever you deploy via compose is called a “stack,” and it contains multiple “services” as defined in your file.

Check the status of the deployment to ensure it scheduled properly:

docker stack ps gitea

Inspect the logs to ensure successful startup:

docker service logs gitea_gitea

You will notice that Caddy automatically intercepts the traffic, redirects it to HTTPS, and provisions Let’s Encrypt certificates on the fly.

Access and Install Gitea

Open your browser and navigate to gitea.example.com. It will automatically redirect securely to https://gitea.example.com (ensure you replace example.com with your actual domain).

Ensure you have configured a DNS A-Record or CNAME pointing gitea.example.com to your Swarm ingress load balancer.


Reference Images from the Deployment:

Gitea Stack Logs

Gitea Initial Configuration

Gitea Admin Account Creation

Gitea Landing Page

Gitea Login Page

Gitea Home Page

Gitea Repository

The deployment of Gitea behind Caddy in our Docker Swarm cluster is complete!

If you enjoyed this tutorial, please share your thoughts in the comments below. It helps me bring more self-hosted open-source content to the community.

Stay tuned for more deployment guides!

Share
Written by
Rajasekhar Gundala

Senior Infrastructure & Web Platform Leader.

Continue reading

Weekly Engineering Notes.

A weekly digest on infrastructure, observability, Rust, and the open web. No spam, just technical signals.

Free. Unsubscribe in one click.