How to Install Gitea on a VPS with Docker Compose

Build a private Git server with Gitea, PostgreSQL, HTTPS, SSH cloning and an off-site backup you have actually tested.

Choose a Cloud VPSOfficial Gitea Docker guide
HYEHOST mascot deploying Gitea, PostgreSQL and HTTPS on a private VPS

Gitea is a lightweight, open-source Git service that puts repositories, pull requests, issues, releases, packages and team access on infrastructure you control. It suits a private development team, an internal automation platform or anyone who wants a self-hosted Git server without operating a much heavier application stack.

The container is the easy part. A dependable deployment also needs a private database, a correct public URL, HTTPS, working SSH clones, restricted registration and a backup containing both the database and repository data. This guide builds that complete path on an Ubuntu or Debian VPS.

Plan the Gitea VPS Architecture

Use one Compose project with separate Gitea and PostgreSQL services. Only Gitea's SSH port is exposed directly; its web interface stays on loopback behind Caddy. PostgreSQL remains private on the Docker network.

WorkloadPractical starting pointWatch as usage grows
Personal repositories2 vCPU, 2GB RAM, 40GB SSDRepository and package storage
Small development team2 to 4 vCPU, 4GB RAMIndexing, webhooks and concurrent pushes
Busy internal platform4+ vCPU, 8GB+ RAMPackages, actions, logs and database I/O
CI runnersSeparate runner hostsUntrusted jobs and burst compute

Do not run untrusted CI jobs inside the same application container. Gitea Actions runners execute repository code and should have their own isolation, resources and lifecycle.

Prepare DNS and the VPS

Create an A and AAAA record such as git.example.com pointing to the VPS. Install Docker Engine and Compose using our current Docker guide, then create the project and a protected environment file:

sudo install -d -m 0750 /opt/gitea
sudo chown "$USER":"$USER" /opt/gitea
cd /opt/gitea
umask 077
cat > .env <<'EOF'
POSTGRES_PASSWORD=replace-with-a-long-random-password
EOF

Keep .env out of repositories and backups that are not encrypted. The database is not published to the host, but its password still protects application data if another service reaches the private network.

Deploy Gitea with Docker Compose

Create /opt/gitea/compose.yml. The example pins Gitea to 1.26.4 so upgrades happen deliberately:

services:
  db:
    image: postgres:17-alpine
    restart: unless-stopped
    environment:
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=gitea
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks: [gitea]

  server:
    image: docker.gitea.com/gitea:1.26.4
    restart: unless-stopped
    depends_on: [db]
    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=${POSTGRES_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
    volumes:
      - gitea_data:/data
    ports:
      - "127.0.0.1:3000:3000"
      - "2222:22"
    networks: [gitea]

networks:
  gitea:
volumes:
  gitea_data:
    name: gitea_data
  postgres_data:
    name: gitea_postgres_data

Replace git.example.com, validate the configuration and start both services:

docker compose config
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 server

The Gitea data volume stores repositories, configuration, attachments, keys and other persistent files. PostgreSQL keeps relational state in its own volume. Both are required for a complete recovery.

Publish Gitea Through HTTPS

Install Caddy or another reverse proxy on the host. With DNS resolving to the VPS, a minimal Caddy site is:

git.example.com {
  reverse_proxy 127.0.0.1:3000
}

Allow administrative SSH, HTTP, HTTPS and the dedicated Git SSH port. Do not expose ports 3000 or 5432:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 2222/tcp
sudo ufw enable
sudo ufw status

Gitea relies on the configured ROOT_URL when it generates clone links, redirects and callback URLs. If links point to HTTP or an internal hostname, fix that value before working around the symptom in the proxy. The official reverse proxy documentation covers forwarded headers and sub-path deployments.

Finish the First-Run Setup

Open https://git.example.com. The database values are already supplied through the environment, so create the first administrator and verify the public URL. Then decide whether users may register themselves.

  • Disable open registration for an internal server.
  • Require sign-in to view private-only installations.
  • Protect administrator accounts with strong, unique credentials.
  • Review repository creation limits and package retention.
  • Configure outbound email through an authenticated relay if notifications are required.

Do not expose direct outbound SMTP merely to make notifications work. Use a proper relay with authenticated credentials and a controlled sender domain.

Test HTTPS and SSH Cloning

Create a test repository, add your public SSH key in Gitea and clone it using port 2222:

git clone ssh://git@git.example.com:2222/username/test-repository.git
cd test-repository
printf '# Gitea test\n' > README.md
git add README.md
git commit -m "Test Gitea push"
git push origin main

Also test an HTTPS clone and a pull request through the browser. This proves DNS, TLS, generated clone URLs, SSH forwarding and repository permissions rather than assuming a healthy login page means the service is complete.

Back Up PostgreSQL and Gitea Data

A VPS snapshot is useful before upgrades, but it is not the only backup. Create a consistent database dump and archive the Gitea data volume while the application is stopped:

cd /opt/gitea
mkdir -p backups
docker compose stop server
docker compose exec -T db pg_dump -U gitea gitea \
  | gzip > backups/gitea-db-$(date +%F).sql.gz
docker run --rm \
  -v gitea_data:/source:ro \
  -v "$PWD/backups:/backup" \
  alpine sh -c 'tar -czf /backup/gitea-data-$(date +%F).tar.gz -C /source .'
docker compose start server

Encrypt and copy the archives to independent storage. Our rclone Storage Box guide provides a practical SFTP workflow. Periodically restore the database and data volume into an isolated test deployment, then clone a repository and inspect issues and attachments.

Update Gitea Without Guesswork

Read the release notes, make a fresh backup and change the pinned Gitea image tag. Pull and recreate the service:

cd /opt/gitea
docker compose pull
docker compose up -d
docker compose logs --tail=100 server

Check the administration dashboard, clone and push over both protocols, and inspect background jobs after each upgrade. Avoid jumping several major versions without reading the documented upgrade path.

Common Gitea VPS Problems

The proxy returns 502 Bad Gateway

Confirm the Gitea container is healthy and port 3000 is bound to 127.0.0.1. Check docker compose logs server and test the loopback endpoint before changing firewall rules.

Clone URLs show HTTP or the wrong hostname

Correct GITEA__server__ROOT_URL, DOMAIN and SSH_DOMAIN, then recreate the container. Generated links follow application configuration.

SSH cloning times out

Check that TCP 2222 is open, mapped to container port 22 and shown in Gitea's clone URL. If port 22 is preferred for Git, move host administration SSH first and verify access before changing it.

Gitea cannot connect to PostgreSQL

Check that both services share the gitea network, the host is db:5432, and the password matches the environment file. Do not replace the service name with localhost.

Frequently Asked Questions

Can Gitea run on a VPS?

Yes. Gitea is lightweight and works well on a Linux VPS sized for the repositories, users, webhooks, packages and indexing load.

How much RAM does Gitea need?

A small private server can start with 2GB RAM. Choose 4GB or more for a team, package storage, larger indexes or extra services.

Is Gitea a GitHub alternative?

Gitea covers private Git hosting, pull requests, issues, packages and integrations on infrastructure you control. GitHub has a broader hosted ecosystem; Gitea trades that convenience for ownership and a smaller operational footprint.

Should port 3000 be public?

No. Bind the Gitea web port to loopback and publish it through an HTTPS reverse proxy.

Should PostgreSQL be public?

No. Keep PostgreSQL on the private Compose network and do not publish port 5432.

What must a Gitea backup contain?

Keep the PostgreSQL database, Gitea data volume, repositories, attachments, configuration and secrets in one encrypted recovery set, stored away from the VPS.