Gitea is a lightweight, self-hosted Git service: repositories, pull requests, issues, wikis, packages and CI (Gitea Actions, compatible with GitHub Actions workflows). It runs comfortably on a small VPS — a few hundred megabytes of RAM for a team — which makes it a popular alternative to GitHub or GitLab for private code.

(Prefer a community-governed fork? Forgejo is a drop-in alternative with nearly identical setup — see installing Forgejo.)

1. Prepare

You need Docker and Compose (guide) and a domain such as git.example.com pointing to the server.

Gitea needs SSH for git clone git@…. Your server’s own SSH already uses port 22, so the simplest approach is to give Gitea’s SSH port 2222.

2. Compose file

mkdir -p ~/gitea && cd ~/gitea

compose.yaml:

services:
  gitea:
    image: gitea/gitea:1
    container_name: gitea
    restart: unless-stopped
    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: ${DB_PASSWORD}
      GITEA__server__DOMAIN: git.example.com
      GITEA__server__ROOT_URL: https://git.example.com/
      GITEA__server__SSH_DOMAIN: git.example.com
      GITEA__server__SSH_PORT: "2222"
      GITEA__service__DISABLE_REGISTRATION: "true"
    volumes:
      - ./data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "127.0.0.1:3000:3000"
      - "2222:22"
    depends_on:
      - db

  db:
    image: postgres:17
    restart: unless-stopped
    environment:
      POSTGRES_USER: gitea
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: gitea
    volumes:
      - ./postgres:/var/lib/postgresql/data
echo "DB_PASSWORD=$(openssl rand -base64 24)" > .env
docker compose up -d

The GITEA__section__KEY variables write into Gitea’s app.ini. Pin a specific version tag (e.g. gitea/gitea:1.24) for predictable upgrades.

Open the SSH port:

sudo ufw allow 2222/tcp

3. HTTPS reverse proxy

server {
    listen 80;
    server_name git.example.com;

    client_max_body_size 512M;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d git.example.com

client_max_body_size matters for pushes over HTTPS and for package/LFS uploads.

4. Initial setup

Open https://git.example.com. The installation page is prefilled from the environment variables — check the values and, at the bottom, create the administrator account. With DISABLE_REGISTRATION on, only admins can create users afterwards (Site Administration → User Accounts).

5. Use it

Add your SSH public key under Settings → SSH / GPG Keys, create a repository, then:

git clone ssh://[email protected]:2222/alice/project.git
# or HTTPS
git clone https://git.example.com/alice/project.git

Save the port in ~/.ssh/config so normal git@ URLs work:

Host git.example.com
    Port 2222
    User git

Migrating from GitHub/GitLab? New Migration imports repositories along with issues, pull requests, releases and wikis.

6. Gitea Actions (CI/CD)

Gitea Actions runs workflows with GitHub Actions syntax. Enable it (on by default in recent versions) and register a runner:

  1. Site Administration → Actions → Runners → Create new runner → copy the token.
  2. Run act_runner (ideally on a separate VPS, since CI jobs run arbitrary code):
services:
  runner:
    image: gitea/act_runner:latest
    restart: unless-stopped
    environment:
      GITEA_INSTANCE_URL: https://git.example.com
      GITEA_RUNNER_REGISTRATION_TOKEN: ${RUNNER_TOKEN}
      GITEA_RUNNER_NAME: runner-1
    volumes:
      - ./runner-data:/data
      - /var/run/docker.sock:/var/run/docker.sock

Then add .gitea/workflows/ci.yml to a repository. See Gitea Actions.

7. Mail

Configure SMTP so users get notifications and password resets:

      GITEA__mailer__ENABLED: "true"
      GITEA__mailer__PROTOCOL: smtp+starttls
      GITEA__mailer__SMTP_ADDR: smtp.example.com
      GITEA__mailer__SMTP_PORT: "587"
      GITEA__mailer__FROM: [email protected]
      GITEA__mailer__USER: [email protected]
      GITEA__mailer__PASSWD: ${SMTP_PASSWORD}

8. Backups

Gitea’s dump command bundles repositories, database and config:

docker exec -u git gitea gitea dump -c /data/gitea/conf/app.ini -f /data/gitea-dump-$(date +%F).zip

Copy the archive off the server regularly (or back up ./data and a pg_dump of the database). See pg_dump and automated backups.

9. Updates

docker compose pull && docker compose up -d

Read release notes for breaking changes; back up first.

Gitea vs GitLab

GitLab is a complete DevOps platform but needs 4–8 GB of RAM; Gitea does the core Git hosting, code review and CI in a fraction of the resources. For individuals and small teams on a VPS, Gitea is usually the better fit. See self-hosted Git options.

A Docker VPS with 2 GB RAM runs Gitea and PostgreSQL easily; put CI runners on a separate server.

  • Gitea
  • Git
  • Self-Hosting