Easily Build Your Private Git Server With Caddy and Containers (Git Over HTTPS)

AI generated + manually edited, some basic knowledge about containers (Docker/Podman) is omitted in this article

Want to have a private Git server accessible via HTTPS like GitHub, but don’t want to deploy complex systems like GitLab or Gitea? This article will show you how to use Caddy2 and Docker to quickly set up your own Git service in a lightweight, efficient, and secure way within minutes.

Final Result

  • Lightweight and Efficient: Based on Caddy and native Git, with extremely low resource usage.
  • Automatic HTTPS: Caddy automatically manages TLS certificates, all transmissions are encrypted.
  • Simple Authentication: Uses Caddy’s built-in basic_auth for username/password protection.
  • Data Persistence: Git repositories and Caddy certificates are persisted through Docker Volumes, ensuring data security.
  • Easy Maintenance: All components are packaged in containers, one-click start/stop, easy migration.

Prerequisites

Before starting, please ensure you have:

  1. A server with Podman (or Docker and Docker Compose) installed.
  2. A domain name pointing to your server IP (e.g., git.your-domain.com).
  3. Server ports 80 and 443 are open for Caddy to request certificates and provide HTTPS service.

Directory Structure

For easy management, let’s first create the following directory and file structure:

/my-caddy-git-server
├── Caddyfile
├── compose.yml
├── custom-containerfile/
│   └── Containerfile
├── git-repos/
│   └── (Git repositories will be stored here)
└── caddy_data/
    └── (Caddy certificates and state will be stored here)

Step 1: Build Caddy Image with caddy-cgi Plugin

We need a custom Caddy image that includes the caddy-cgi plugin so Caddy can execute the git-http-backend program.

Write the following content in custom-containerfile/Containerfile:

# Stage 1: Builder - Only used to compile Caddy with plugins
FROM docker.io/library/caddy:2-builder AS builder

# Use xcaddy to build Caddy with cgi plugin
# caddy-cgi plugin allows Caddy to act as a CGI gateway
RUN xcaddy build \
    --with github.com/aksdb/caddy-cgi/v2

# Stage 2: Final runtime image
FROM docker.io/library/caddy:latest

# Install git-daemon in the final image so git-http-backend is available in runtime
RUN apk update && apk add --no-cache git git-daemon

# Copy the compiled Caddy binary with plugins from builder stage
COPY --from=builder /usr/bin/caddy /usr/bin/caddy

This Containerfile uses multi-stage builds, with the final image containing only the necessary git-daemon and Caddy with plugins, very streamlined.

Step 2: Configure Caddyfile

Caddyfile is Caddy’s configuration file, we use it to define Git service behavior.

Write the following content in Caddyfile in the project root directory:

# Your Git service domain
git.your-domain.com {
    # Enable basic authentication to protect the entire service
    basic_auth {
        # Use `caddy hash-password` command to generate your password hash
        # Replace "your_username" with your username
        # Replace "$2a$14$..." with the generated hash password
        your_username $2a$14$ABC...
    }

    # Route all requests to git-http-backend
    # In Alpine images, git-http-backend path is usually this
    cgi * /usr/libexec/git-core/git-http-backend {
        # Key: Set environment variables to tell Git backend how to work
        # GIT_PROJECT_ROOT: Root directory of Git repositories, must match mount path in compose.yml
        # GIT_HTTP_EXPORT_ALL: Allow clients to discover all repositories without extra configuration
        env GIT_PROJECT_ROOT=/git-repos GIT_HTTP_EXPORT_ALL=1
    }
}

Step 3: Write Compose File

The compose.yml file connects all parts (custom image, configuration files, data volumes) together.

Write the following content in compose.yml in the project root directory:

name: git-server
services:
  git-server:
    # Specify using our custom Containerfile for building
    build: ./custom-containerfile
    restart: unless-stopped
    ports:
      # Map HTTPS and HTTP ports
      - "443:443"
      - "80:80"
    volumes:
      # Mount Caddyfile
      - ./Caddyfile:/etc/caddy/Caddyfile
      # Mount directory for storing Git repositories
      - ./git-repos:/git-repos
      # Mount Caddy data volume for persisting TLS certificates
      - ./caddy_data:/data

Step 4: Start Service and Create Repositories

  1. Start Service In the my-caddy-git-server directory, run the following command to start the service:

    podman-compose up -d

    Compose will automatically build the image, create and start the container. Caddy will request and configure HTTPS certificates for your domain.

  2. Create Your First Repository After the service starts, create your first bare repository in the host’s git-repos directory.

    # Enter the repository storage directory
    cd git-repos
    
    # Create a bare repository named my-project.git
    git init --bare my-project.git
  3. (Optional) Migrate Existing Repository If you want to migrate a repository from GitHub or elsewhere, you can use the --mirror option.

    # This will create a mirrored bare repository my-awesome-project.git in current directory
    git clone --mirror https://github.com/someuser/my-awesome-project.git

Step 5: Clone and Push

Everything is ready! Now you can access your Git repositories via HTTPS from any machine. Clone URL format is https://<your-domain>/<repository-name>.git.

Clone repository:

git clone https://git.your-domain.com/my-project.git

Git will prompt you for the username and password configured in Caddyfile. After authentication, the repository will be successfully cloned.

Push updates:

cd my-project
echo "Hello, private Git server!" > README.md
git add .
git commit -m "Initial commit"
git push origin main

Similarly, after entering credentials, your code will be securely pushed to the private server.

Summary

Using Caddy and containers, we implemented a fully functional, secure, and reliable private Git server with minimal configuration. Compared to GitLab/Gitea solutions, it has extremely low resource usage, low maintenance costs, and enjoys the convenience of automatic HTTPS from Caddy. I hope this tutorial helps you easily control your own code.


Appendix: About Git Over SSH

This article focuses on Git over HTTPS. If you prefer using SSH, you actually don’t need Caddy or containers.

You just need a standard user account on your server with Git installed. Then:

  1. Create a bare repository on the server (git init --bare my-repo.git).
  2. Ensure your SSH public key is in the server user’s ~/.ssh/authorized_keys file.
  3. Use SSH protocol for cloning: git clone user@your-domain.com:/path/to/my-repo.git.

This approach relies on the system’s SSH service and is completely independent from the HTTPS service described in this article.

Advanced: Using CGI to Display Git Repositories

Using Caddy Container and Cgit to Display Git Repositories

0%