The Way to Your Own Cloud (Part 11) – Run your own GitHub

GitHub, GitLab or Bitbucket – convenient, but all hosted elsewhere. If you truly want independence, you should also host your source code repositories yourself.

With Gitea you get a lightweight, fast, and flexible Git web interface. It offers everything you know: issues, pull requests, wikis, organizations – and all of it open source.

Setup

Gitea supports PostgreSQL. So first, create a user and a database:

sudo -u postgres psql

Inside the psql shell, execute the following statements:

CREATE USER giteauser WITH PASSWORD '<STRONG-PASSWORD>';

CREATE DATABASE giteadb WITH OWNER giteauser;

GRANT ALL PRIVILEGES ON DATABASE giteadb TO giteauser;

Exit the tool with \\q or exit.

The service will run under the directory /opt/gitea:

sudo mkdir -p /opt/gitea

cd /opt/gitea

Create the file docker-compose.yaml there:

services:
  gitea:
    image: localhost:5000/gitea/gitea:latest
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=host.docker.internal:5432
      - GITEA__database__NAME=giteadb
      - GITEA__database__USER=giteauser
      - GITEA__database__PASSWD=<STRONG-PASSWORD>
    volumes:
      - ./data:/data
    ports:
      - "4000:3000"
      - "2222:22"
    restart: unless-stopped
    extra_hosts:
      - "host.docker.internal:host-gateway"

Next, host the underlying image from the official hub locally:

docker pull gitea/gitea:latest

docker tag gitea/gitea:latest localhost:5000/gitea/gitea:latest

docker push localhost:5000/gitea/gitea:latest

Now start the service:

docker compose up -d

For security reasons, create a random port:

echo $(shuf -i 1024-65535 -n 1)

For external communication, NGINX requires an additional configuration in the file /etc/nginx/sites-available/gitea:

server {
    listen <RANDOM-PORT> ssl;
    server_name <CLOUD-DOMAIN>;

    ssl_certificate /etc/letsencrypt/live/<CLOUD-DOMAIN>/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/<CLOUD-DOMAIN>/privkey.pem;

    location / {
        proxy_pass http://localhost:4000;
        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;
    }
}

Activate and reload with:

sudo ln -s /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/

sudo systemctl restart nginx

Finally, open the port in the firewall:

sudo ufw allow <RANDOM-PORT>

sudo ufw reload

Now Gitea should be available at https://<CLOUD-DOMAIN>:<RANDOM-PORT>.

Backups

Gitea can also be integrated into the central backup system. Just create a file named /opt/backup/plugins/gitea.sh with the following content:

#!/bin/bash

PLUGIN_OUTPUT_DIR="$1"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S.%3N')] $*"
}

FILES_BACKUP="$PLUGIN_OUTPUT_DIR/files_$(date '+%Y-%m-%d').tar.gz"

log "[INFO] Start backup of files in $GITEA_DATA_DIR ..."
tar -czf "$FILES_BACKUP" -C "$GITEA_DATA_DIR" .

if [ $? -eq 0 ]; then
    log "[INFO] Backup of files was successful: $FILES_BACKUP"
else
    log "[ERROR] Backup of files failed!"
fi

Then make it executable:

chmod +x /opt/backup/plugins/gitea.sh

Conclusion

With Gitea you run a lean, fast, and secure alternative to GitHub & Co. Repositories, issues, and wikis remain fully under your own control – perfect for small teams, projects, or as your personal GitHub at home.

Quick & Dirty

DB_PASS='STRONG_PASSWORD'
DOMAIN='cloud.example.com'
PORT=$(shuf -i 1024-65535 -n 1)

sudo -u postgres psql <<EOF
CREATE USER giteauser WITH PASSWORD '$DB_PASS';
CREATE DATABASE giteadb WITH OWNER giteauser;
GRANT ALL PRIVILEGES ON DATABASE giteadb TO giteauser;
\\q
EOF

sudo mkdir -p /opt/gitea
cd /opt/gitea

cat <<EOF > docker-compose.yaml
services:
  gitea:
    image: localhost:5000/gitea/gitea:latest
    container_name: gitea
    environment:
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=host.docker.internal:5432
      - GITEA__database__NAME=giteadb
      - GITEA__database__USER=giteauser
      - GITEA__database__PASSWD=$DB_PASS
    volumes:
      - ./data:/data
    ports:
      - "3000:3000"
      - "2222:22"
    restart: unless-stopped
    extra_hosts:
      - "host.docker.internal:host-gateway"
EOF

docker pull gitea/gitea:latest
docker tag gitea/gitea:latest localhost:5000/gitea/gitea:latest
docker push localhost:5000/gitea/gitea:latest
docker compose up -d

sudo tee /etc/nginx/sites-available/gitea >/dev/null <<EONGX
server {
    listen $PORT ssl;
    server_name $DOMAIN;
    ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem;
    location / {
        proxy_pass http://localhost: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;
    }
}
EONGX

sudo ln -sf /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/
sudo systemctl restart nginx
sudo ufw allow $PORT && sudo ufw reload

cat <<'EOF' > /opt/backup/plugins/gitea.sh
#!/bin/bash
PLUGIN_OUTPUT_DIR="$1"
GITEA_DATA_DIR="/opt/gitea/data"

tar -czf "$PLUGIN_OUTPUT_DIR/files_$(date '+%Y-%m-%d').tar.gz" -C "$GITEA_DATA_DIR" .
EOF

chmod +x /opt/backup/plugins/gitea.sh