Install Gitea with Docker: Your Own Git Server (A Step-by-Step Guide)

Install Gitea with Docker and Caddy as a reverse proxy. A beginner-friendly, step-by-step guide to setting up your own lightweight Git server.

Install Gitea with Docker: Your Own Git Server (A Step-by-Step Guide) hero image

Have you ever wanted to host your own Git repositories—similar to GitHub or GitLab, but perhaps a bit more lightweight and with full control on your own server? In this article, I’ll show you step-by-step how to install Gitea, a self-hosted, open-source Git service, using Docker on your own server. Gitea is written in Go, which makes it performant and resource-efficient—perfect for a home server, small teams, or the individual developer who wants to maintain control over their code.

We’ll walk through the entire process together: from the Docker setup with Docker Compose, to configuring a reverse proxy (I’m using Caddy here), all the way to the initial setup of Gitea itself and important considerations for backups.

What You’ll Need

Before we start, here’s a quick checklist of the prerequisites:

  1. A Server: This could be a VPS from a hosting provider like Hetzner or Netcup, or even your Raspberry Pi or another server at home. The important thing is that you have SSH access.
  2. Docker and Docker Compose: Must be installable on the server. We will go through the installation together.
  3. A Domain: Or at least a subdomain that points to your server’s IP address. Without a domain, it will be difficult to access Gitea properly and securely.
  4. A Reverse Proxy: I’ll demonstrate the setup with Caddy here. However, if you already use Nginx Proxy Manager or Traefik, you can certainly use those as well. The principle remains the same.
  5. (Optional) Home server without a public IP: If you’re using a home server that isn’t directly accessible from the internet, you could use Pangolin.

Step 1: Server Preparation and Docker Installation

First, let’s connect to our server and bring it up to date. I use VS Code with the “Remote - SSH” extension for this, but any other SSH terminal works just as well.

DNS Check and SSH Connection

Make sure your domain correctly points to your server’s IP address. A simple ping command in your terminal will help:

ping gitea.yourdomain.com

If the correct IP address responds, everything is ready. Now, connect to your server via SSH.

ssh username@YOUR_SERVER_IP

System Update and Docker Installation

Once logged into the server, let’s update the package lists and installed packages:

sudo apt update
sudo apt upgrade -y

Although Docker can be installed from the standard package repositories, I recommend using the official installation script to ensure you get the latest version. You can find the official instructions on the Docker website.

Here are the commands to quickly set up Docker and Docker Compose:

# Remove old Docker versions
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done

# Prepare installation packages for Docker:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the Docker repository:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
# Install Docker packages:
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y

Verify the installation with:

docker -v
# Docker version 28.0.3, build ...
docker compose version
# Docker Compose version v2.32.0

Step 2: Set Up Gitea with Docker Compose

Now that Docker is running, we can set up Gitea. We’ll start by creating the necessary folder structure.

# Create a folder for Gitea and change into it
mkdir gitea
cd gitea

# Create a subfolder for persistent data
mkdir data

Now, create a docker-compose.yml file:

touch docker-compose.yml

Open this file and add the following content. We’ll follow the official Docker guide from Gitea.

networks:
    gitea:
        external: false

services:
    server:
        image: gitea/gitea:1.24
        container_name: gitea
        environment:
            - USER_UID=1000
            - USER_GID=1000
        restart: unless-stopped
        networks:
            - gitea
        volumes:
            - ./data:/data
            - /etc/timezone:/etc/timezone:ro
            - /etc/localtime:/etc/localtime:ro
        ports:
            - "3000:3000"
            - "2222:22"

A few important notes about this configuration:

  • image: gitea/gitea:1.24: It’s a best practice to use a specific major version (like 1.24) instead of latest. This prevents unexpected breaking changes during updates. When you run docker compose pull, you will still get patch updates (e.g., from 1.24.7 to 1.24.8).
  • USER_UID & USER_GID: These should match the ID of your user on the host system to avoid permission issues with the volumes. Find your IDs with the id command in the terminal.
  • volumes: - ./data:/data: We are mapping the local data folder into the container. This is where Gitea will store all its configurations, databases (if using SQLite), and repositories.
  • ports: We are forwarding the web port 3000 and the SSH port 22 from the container to the host ports 3000 and 2222. The SSH port is mapped to 2222 to avoid conflicts with the host system’s SSH service.

Now, start the container:

sudo docker compose up -d

This command will download the Gitea image and start the container in the background. You can monitor resource usage with sudo docker stats.

Step 3: Gitea’s Initial Configuration (with SQLite)

Now, open your browser and navigate to http://YOUR_SERVER_IP:3000. You will be greeted by the Gitea installation page.

For a simple setup, we’ll choose SQLite3 as the database type. Make sure to adjust the path so that it writes to our mapped volume:

  • Database Path: /data/gitea/gitea.db

Other important settings:

  • Site Title: Give your instance a name, e.g., “My Git Server”.
  • Repository Root Path: Leave it at /data/git/gitea-repositories so it will be stored persistently.
  • Application Base URL: This should be http://YOUR_SERVER_IP:3000/.
  • Optional Settings: Disable user registration if you only want to use the instance for yourself.
  • Administrator Account: Create your admin account at the very bottom of the page.

Click “Install Gitea”. After a brief moment, you should land on your brand-new Gitea dashboard!

You can now create your first repository and use it just like you would on GitHub or GitLab.

Step 4: Robust Installation with PostgreSQL and a Caddy Reverse Proxy

The SQLite setup is simple, but for a more robust and scalable solution, especially if multiple users are planned, a database like PostgreSQL is the better choice. At the same time, we want to make Gitea accessible via a secure HTTPS domain instead of an IP and port.

To do this, we’ll shut down our current installation and delete the old data to start fresh.

# In the gitea folder
sudo docker compose down

# Delete the old data folder and recreate it for a clean start
sudo rm -rf ./data
mkdir data

# We'll create a folder for the database
mkdir postgres

Adjusting Docker Compose with PostgreSQL

We’ll extend our docker-compose.yml to add a PostgreSQL container.

networks:
    gitea:
        external: false
    proxy:
        external: true

services:
    server:
        image: gitea/gitea:1.24
        container_name: gitea
        environment:
            - USER_UID=1000
            - USER_GID=1000
            - GITEA__database__DB_TYPE=postgres
            - GITEA__database__HOST=db:5432
            - GITEA__database__NAME=gitea
            - GITEA__database__USER=gitea
            - GITEA__database__PASSWD=a_secure_password
        restart: unless-stopped
        networks:
            - gitea
            - proxy
        volumes:
            - ./data:/data
            - /etc/timezone:/etc/timezone:ro
            - /etc/localtime:/etc/localtime:ro
        depends_on:
            db:
                condition: service_healthy

    db:
        image: postgres:17
        container_name: gitea-db
        restart: unless-stopped
        environment:
            - POSTGRES_USER=gitea
            - POSTGRES_PASSWORD=a_secure_password
            - POSTGRES_DB=gitea
        networks:
            - gitea
        volumes:
            - ./postgres:/var/lib/postgresql/data
        healthcheck:
            test: ["CMD-SHELL", "pg_isready -U gitea -d gitea"]
            interval: 10s
            timeout: 5s
            retries: 5

Important changes:

  • We’ve added a db service for PostgreSQL and created a postgres folder for its data (mkdir postgres).
  • The environment variables in the server service configure Gitea to connect to the db. Important: HOST is db, the service name of the database container.
  • depends_on with condition: service_healthy ensures that Gitea only starts once the database is ready.
  • We have removed the ports and added the Gitea container to an external proxy network.

Setting Up Caddy as a Reverse Proxy

Caddy is a modern web server that automatically obtains and renews HTTPS certificates from Let’s Encrypt.

First, create the external Docker network:

sudo docker network create proxy

Now, let’s create a separate folder structure for Caddy:

cd ..  # Back out of the gitea folder
mkdir caddy
cd caddy
mkdir data config
touch docker-compose.yml Caddyfile

Add the following to caddy/docker-compose.yml:

networks:
    proxy:
        external: true

services:
    caddy:
        image: caddy:latest
        container_name: caddy
        restart: unless-stopped
        ports:
            - "80:80"
            - "443:443"
        volumes:
            - ./Caddyfile:/etc/caddy/Caddyfile
            - ./data:/data
            - ./config:/config
        networks:
            - proxy

And in the Caddyfile, add the configuration for the reverse proxy:

gitea.yourdomain.com {
    encode zstd gzip
    reverse_proxy gitea:3000
}
  • Replace gitea.yourdomain.com with your domain.
  • reverse_proxy gitea:3000 forwards all requests for this domain to the Gitea container (whose container_name is gitea) on port 3000.

Start Caddy:

# In the caddy folder
sudo docker compose up -d

Starting Gitea with the New Configuration

Change back to the gitea folder and start the Gitea and database containers:

# In the gitea folder
sudo docker compose up -d

Now, navigate to your domain (e.g., https://gitea.yourdomain.com). You will see the installation page again. The database settings should already be correctly pre-filled for PostgreSQL. Adjust the Server Domain and Gitea Base URL to match your domain:

  • Server Domain: gitea.yourdomain.com
  • Gitea Base URL: https://gitea.yourdomain.com/

Complete the installation as before.

Step 5: Setting Up Backups

A self-hosted service is only as good as its backup. We’ll back up two things: the Gitea data (repositories, configuration) and the PostgreSQL database.

You can back up the gitea/data folder with a tool like Duplicati. For the database, we’ll add another service to our gitea/docker-compose.yml that creates daily dumps.

Add this service to the end of your gitea/docker-compose.yml:

# ... (inside services:)
backup:
    image: postgres:17
    container_name: gitea-db-backup
    restart: unless-stopped
    volumes:
        - ./backup-db:/backup
    networks:
        - gitea
    environment:
        - PGHOST=db
        - PGUSER=gitea
        - PGPASSWORD=a_secure_password
        - PGDATABASE=gitea
        - SLEEP_TIME=86400 # 24 hours in seconds
    entrypoint: |
        bash -c '
          while true; do
            echo "Creating database backup..."
            pg_dump -Fc > /backup/gitea_dump_$(date +%Y-%m-%d_%H-%M-%S).dump
            echo "Backup created. Sleeping for $$SLEEP_TIME seconds."
            sleep $$SLEEP_TIME
          done
        '
    depends_on:
        db:
            condition: service_healthy

Also, create the folder for the backups: mkdir backup-db. After a restart with sudo docker compose down followed by sudo docker compose up -d, this container will create a daily backup of the database in the backup-db folder. You should then also back up this folder externally using Duplicati or another tool.

Important: Test your backups regularly!

Practical Use and Tips

Pushing and Migrating Repositories

You can now push local projects to your Gitea server:

# In a local Git repository
git remote add gitea https://gitea.yourdomain.com/YOUR_USERNAME/PROJECT.git
git push gitea main

Alternatively, you can use the migration feature to import repositories directly from GitHub, GitLab, or other Git services. To do this, click the + icon in the top right and select “New Migration”.

Private vs. Public Repositories

By default, newly created repositories are public if your Gitea instance is publicly accessible. If you don’t want this, you have two options:

  1. When creating a repository, check the box for “This repository is private”.
  2. Disable registration and set the REQUIRE_SIGNIN_VIEW = true option in the data/gitea/conf/app.ini to force users to be logged in to see anything.

Troubleshooting

  • Links or clone URLs are incorrect: Check the ROOT_URL in your data/gitea/conf/app.ini and restart Gitea.
  • Permission issues: Ensure that the USER_UID and USER_GID in the docker-compose.yml are correct and that the user has write permissions for the mapped folders (data, postgres, backup-db).
  • Problems after startup: Check the logs with sudo docker compose logs. You will often find helpful error messages there.

Conclusion

Congratulations! You have successfully installed Gitea with Docker, made it securely accessible via a reverse proxy, and you know how to back up your valuable repositories. You now have your own fast, private Git server under your full control.

I hope you enjoyed this tutorial and found it helpful. Are you already using Gitea or another self-hosted Git server? What features are most important to you? Thanks for reading, and have fun with your own Gitea

Share this post:

This website uses cookies. These are necessary for the functionality of the website. You can find more information in the privacy policy