Install Gitea on Proxmox VE

Why Proxmox for Gitea

Proxmox gives you isolated containers with thin resource allocation. Gitea in an LXC container uses a fraction of the overhead of a full VM — 2 vCPUs and 2 GB RAM is plenty. You get Proxmox’s snapshot and backup tooling, plus the ability to bind-mount host storage so repository data persists across container rebuilds.

Prerequisites

  • Proxmox VE 8.x installed and accessible via web UI
  • A CT template downloaded (Ubuntu 22.04 or Debian 12 recommended)
  • At least 2 GB RAM and 10 GB disk to allocate to the container
  • Storage on the Proxmox host for bind-mounting repository data (local disk, ZFS dataset, or NFS)
  • Network access to the container from your workstation

Create the LXC Container

Via Proxmox Web UI

  1. Go to Datacenter > Storage and ensure you have a CT template (download Ubuntu 22.04 from the template list if needed)
  2. Click Create CT
  3. Configure:
    • Hostname: gitea
    • Password: set a root password
    • Template: Ubuntu 22.04 (or Debian 12)
    • Disk: 10 GB (repositories stored via bind mount, not here)
    • CPU: 2 cores
    • Memory: 2048 MB
    • Swap: 512 MB
    • Network: DHCP or static IP on your bridge

Via CLI

# On the Proxmox host
pct create 110 local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst \
  --hostname gitea \
  --cores 2 \
  --memory 2048 \
  --swap 512 \
  --rootfs local-lvm:10 \
  --net0 name=eth0,bridge=vmbr0,ip=dhcp \
  --features nesting=1,keyctl=1 \
  --unprivileged 1

The nesting=1 and keyctl=1 features are required for Docker inside LXC.

Enable Docker Support in LXC

Docker requires specific LXC configuration. On the Proxmox host, edit the container config:

nano /etc/pve/lxc/110.conf

Add these lines:

lxc.apparmor.profile: unconfined
lxc.cap.drop:
lxc.mount.auto: proc:rw sys:rw

If using an unprivileged container (recommended), you also need:

lxc.cgroup2.devices.allow: a

Bind Mount for Repository Storage

Create a directory on the Proxmox host for persistent git data:

mkdir -p /mnt/data/gitea

Add the bind mount to the container config (/etc/pve/lxc/110.conf):

mp0: /mnt/data/gitea,mp=/mnt/gitea

This maps the host’s /mnt/data/gitea to /mnt/gitea inside the container. Repository data survives container rebuilds.

Start the container:

pct start 110

Install Docker Inside the LXC

Enter the container:

pct enter 110

Install Docker:

apt update && apt install -y ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null

apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

Verify Docker runs:

docker run --rm hello-world

Docker Compose Configuration

Create the project directory:

mkdir -p /opt/gitea && cd /opt/gitea

Create docker-compose.yml:

services:
  gitea:
    image: gitea/gitea:1.25.4
    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=secure_password_here  # Change this
      - GITEA__server__ROOT_URL=http://gitea.yourdomain.com:3000/  # Change this
      - GITEA__server__SSH_PORT=2222
      - GITEA__server__SSH_DOMAIN=gitea.yourdomain.com  # Change this
    restart: unless-stopped
    volumes:
      - /mnt/gitea/data:/data          # Bind mount from Proxmox host
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "2222:22"
    depends_on:
      db:
        condition: service_healthy
    networks:
      - gitea-net

  db:
    image: postgres:16-alpine
    container_name: gitea-db
    restart: unless-stopped
    environment:
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=secure_password_here  # Must match GITEA__database__PASSWD
      - POSTGRES_DB=gitea
    volumes:
      - /mnt/gitea/postgres:/var/lib/postgresql/data  # Bind mount from Proxmox host
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "gitea"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - gitea-net

networks:
  gitea-net:

Both Gitea data and PostgreSQL data use bind mounts to /mnt/gitea/ — the directory that maps to the Proxmox host via the bind mount configured earlier. This means all critical data lives on the host, not inside the container’s root filesystem.

Start the stack:

docker compose up -d

First-Time Setup

Open http://<container-ip>:3000 in your browser and complete the setup wizard:

  1. Verify database settings match your environment variables
  2. Set Server Domain to your container’s IP or hostname
  3. Set SSH Server Port to 2222
  4. Create an admin account
  5. Click Install Gitea

SSH Port Forwarding Through Proxmox

If you need to access Gitea SSH from outside the Proxmox host’s network, forward ports on the host. For a bridged network (most common), the container has its own IP and is directly reachable — no forwarding needed.

For NAT setups, add iptables rules on the Proxmox host:

iptables -t nat -A PREROUTING -i vmbr0 -p tcp --dport 2222 \
  -j DNAT --to-destination <container-ip>:2222

Proxmox Backup Strategy

Stop-and-Snapshot Approach

For consistent backups, stop Gitea before taking a Proxmox snapshot. Git repositories can be corrupted if snapshotted mid-write.

Create a backup script on the Proxmox host:

#!/bin/bash
# /usr/local/bin/backup-gitea.sh
CT_ID=110

echo "Stopping Gitea containers..."
pct exec $CT_ID -- docker compose -f /opt/gitea/docker-compose.yml stop

echo "Creating backup..."
vzdump $CT_ID --storage local --compress zstd --mode stop

echo "Starting Gitea containers..."
pct exec $CT_ID -- docker compose -f /opt/gitea/docker-compose.yml start

echo "Backup complete."

Database-Only Backup (No Downtime)

For more frequent backups without stopping the service:

#!/bin/bash
# Run inside the LXC container
BACKUP_DIR="/mnt/gitea/backups/$(date +%Y-%m-%d)"
mkdir -p "$BACKUP_DIR"
docker exec gitea-db pg_dump -U gitea gitea > "$BACKUP_DIR/gitea-db.sql"

The database dump is crash-consistent. Combined with the bind-mounted repository files (which are just bare git repos on disk), this gives you a complete backup.

Schedule with cron on the Proxmox host:

# Nightly full backup at 2 AM
0 2 * * * /usr/local/bin/backup-gitea.sh

See Backup Strategy for the full 3-2-1 approach.

Proxmox-Specific Tips

Resource Monitoring

Monitor the container’s resource usage from the Proxmox web UI under CT 110 > Summary. Gitea should idle at:

  • CPU: <5%
  • Memory: 300-400 MB (Gitea + PostgreSQL combined)

If memory usage creeps above 1.5 GB, check for runaway git operations or index rebuilds.

Scaling Up

To add resources without rebuilding:

# On Proxmox host -- container must be stopped
pct set 110 --memory 4096
pct set 110 --cores 4

Or adjust live (with hotplug enabled in the container options).

ZFS Storage Advantage

If your Proxmox host uses ZFS, the bind-mounted directory benefits from ZFS snapshots, compression, and checksumming. Create a dedicated ZFS dataset for Gitea:

zfs create rpool/data/gitea
# Then set mp0: /rpool/data/gitea,mp=/mnt/gitea in the LXC config

ZFS snapshots are near-instant and do not require stopping the container (though stopping is still recommended for full consistency).

Troubleshooting

Docker Fails to Start Inside LXC

Symptom: Cannot connect to the Docker daemon or iptables errors.

Fix: Verify the LXC config has nesting=1, keyctl=1, and lxc.apparmor.profile: unconfined. Restart the container after changing config:

pct stop 110 && pct start 110

Permission Denied on Bind Mount

Symptom: Gitea cannot write to /mnt/gitea/data.

Fix: In an unprivileged container, UIDs are remapped. Set ownership on the Proxmox host:

chown -R 101000:101000 /mnt/data/gitea

The 101000 UID corresponds to UID 1000 inside an unprivileged container (100000 + 1000).

Container IP Changes After Reboot

Symptom: Cannot access Gitea after Proxmox reboot because the container got a different DHCP IP.

Fix: Assign a static IP in the container config:

pct set 110 --net0 name=eth0,bridge=vmbr0,ip=192.168.1.50/24,gw=192.168.1.1

Backup Fails With Lock Error

Symptom: vzdump reports a lock error.

Fix: Another backup or operation holds a lock on the container. Wait for it to finish, or clear stale locks:

pct unlock 110

High Disk I/O During Git Operations

Symptom: Other containers on the same host slow down during large git pushes.

Fix: Set I/O limits on the Gitea container to prevent it from starving other workloads:

pct set 110 --mp0 /mnt/data/gitea,mp=/mnt/gitea,size=50G

Or use ZFS I/O priority if running on ZFS storage.

Resource Requirements

  • LXC allocation: 2 vCPU, 2 GB RAM, 10 GB root disk
  • Actual usage: ~300-400 MB RAM idle, <5% CPU
  • Storage: Bind mount sized for your repository needs (plan 1 GB per 10 medium repos)

Comments