Gitea – Your Self-Hosted Git Platform

You feel the comfort of the cloud – low maintenance, fast setup.
Your source code, configuration and files are safe.

Or… are they?

You rely on a third party for infrastructure control, security operations and availability.
What would your business continuity plan look like if your SaaS provider became unavailable?

In this article we investigate a self-hosted solution for small businesses.

Risks In SaaS

You don’t control the underlying infrastructure.
You don’t audit the services they use.
You trust the provider’s security posture.

A private repository is private if the settings are correct.
Your data is safe until the first breach happens.

Local-First Is The Secure Solution

The cloud SaaS is not an enemy – only the way you use it can make it one.
Be selective about what you share.
Keep the critical data inside the office walls.

Here is where Gitea comes into the picture.

This lightweight self-hosted SCM and productivity tool runs on your infrastructure.
It allows you to control data access, retention and network exposure directly.

Either as your local-first SCM, or a mirror of your cloud instances – your control and resiliency increase.

Install The Gitea Server

A typical small installation runs comfortably on 1–2 GB RAM, with much lower idle usage.
It’s so lightweight, it can run on a generic PC, or even a mini-PC.
You own the hardware, your data, your processes.

Gitea requires git installed on your server.

Download the latest binary that matches your architecture.
Always verify the latest stable release at https://dl.gitea.com/gitea/.

wget -O gitea https://dl.gitea.com/gitea/1.25.4/gitea-1.25.4-linux-amd64
chmod +x gitea

Move the binary to the system $PATH.

sudo mv gitea /usr/local/bin/gitea

Create a dedicated user to run the service.
Do not attempt to run Gitea as root.

sudo adduser \
     --system \
     --shell /bin/bash \
     --gecos 'Git Version Control Server' \
     --group \
     --disabled-password \
     --home /home/git \
     git

Create the required directory structure.

sudo mkdir -p /var/lib/gitea/{custom,data,log}
sudo chown -R git:git /var/lib/gitea/
sudo chmod -R 750 /var/lib/gitea/
sudo mkdir /etc/gitea
sudo chown root:git /etc/gitea
sudo chmod 770 /etc/gitea           # Temporary write permission

It gives temporary write permission to /etc/gitea.
Gitea writes app.ini during first start via the web installer.

After the installation reset it.

chmod 750 /etc/gitea
chmod 640 /etc/gitea/app.ini

Configure a gitea systemd service for Gitea as written in the official documentation.

Enable and start the gitea service.

sudo systemctl enable gitea
sudo systemctl start gitea

Enterprise-Ready Database Backend

The default Gitea configuration uses SQLite as its database backend.
If you want concurrency and to scale it better – use PostgreSQL.

Set up the [database] part of the /etc/gitea/app.ini to handle PostgreSQL:

[database]
DB_TYPE  = postgres
HOST     = 127.0.0.1:5432
NAME     = gitea
USER     = gitea
PASSWD   = <your_secure_password>
SSL_MODE = disable

Use SSL_MODE = require if PostgreSQL runs on a separate host.

Create a DB role for Gitea:

CREATE ROLE gitea WITH LOGIN PASSWORD '<your_secure_password>';

Create a database for it:

CREATE DATABASE gitea WITH OWNER gitea TEMPLATE template0 ENCODING UTF8 LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8';

If the DB is local, in the pg_hba.conf set up the user’s permission:

local gitea gitea scram-sha-256

Reverse Proxy And SSL

Never communicate in plain text over the network.
Protect your data in transit with SSL.

You can configure a simple, secure proxy server in minutes.

Edit the site file:

sudo nano /etc/nginx/sites-available/gitea

Configure the reverse proxy in this file:

server {
    listen 80;
    server_name gitea.tomsitcafe.com;

    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;
    }

    client_max_body_size 100M;
}

Adjust client_max_body_size according to your repository sizes.

Enable it:

sudo ln -s /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Add the Certbot HTTPS certificate:

sudo certbot --nginx -d gitea.tomsitcafe.com

Important Gitea server setting:

[server]
DOMAIN    = gitea.tomsitcafe.com
ROOT_URL = https://gitea.tomsitcafe.com/

Make sure that you use HTTPS as the protocol.

This setting gives you a secure base SCM with

  • UI
  • RBAC
  • scalability

Self-Hosted CI/CD System

If you don’t just want to store the source code: act_runner builds you CI/CD.
Gitea Actions is Gitea’s built-in CI/CD tool.

For simplicity, it is recommended to install Docker on the runner host.

Download the actrunner release binary for your architecture.

Move the binary to its place in the system $PATH.

chmod +x act_runner
sudo mv act_runner /usr/local/bin/act_runner

Create a dedicated user to the runner.

sudo adduser \
     --system \
     --shell /bin/bash \
     --gecos 'Gitea act_runner User' \
     --group \
     --disabled-password \
     --home /home/act_runner \
     act_runner

If using Docker, the act_runner user should also be added to the docker group before starting the service.

It requires logout/login or service restart to take effect.

sudo usermod -aG docker act_runner

Warning: Docker can be dangerous.
The users of the docker group are literally root on the host.

Only add trusted users to this group – and to your Gitea server.

Switch to the act_runner user.

sudo -iu act_runner

Generate a configuration file.
Each act_runner instance requires its own directory.

act_runner generate-config > /home/act_runner/<runner-dir>/runner_config.yaml

Edit the configuration for your instance.

Register the runner to the Gitea server.
Perform it in the runner’s config directory otherwise the .runner state file is generated in the wrong place. Alternatively you can use a full path to the file in the configuration file.

cd /home/act_runner/<runner-dir>
act_runner register --no-interactive --instance <instance_url> --token <registration_token> --name <runner_name> --labels <runner_labels> --config <config>

Start the runner.

act_runner daemon --config /home/act_runner/<runner-dir>/runner_config.yaml

Verify that the runner can communicate with the Gitea server.

As root or with sudo: configure the systemd service for the runner.

[Unit]
Description=Gitea Actions runner
Documentation=https://gitea.com/gitea/act_runner
After=docker.service

[Service]
ExecStart=/usr/local/bin/act_runner daemon --config /home/act_runner/<runner-dir>/runner_config.yaml
ExecReload=/bin/kill -s HUP $MAINPID
WorkingDirectory=/home/act_runner/<runner-dir>
TimeoutSec=0
RestartSec=10
Restart=always
User=act_runner

[Install]
WantedBy=multi-user.target

Operational notes:

  • force_pull: false for local images
  • Many community actions (including actions/checkout) require Node.js inside the runner environment.
  • upload-artifact@v4 not supported on Gitea

When Self-Hosting Makes Sense

  • Regulatory or data residency requirements
  • Sensitive intellectual property
  • Need for hybrid redundancy strategy (mirroring)
  • Long-term cost predictability
  • Air-gapped or internal-only environments

Final Thoughts

The cloud and SaaS are not bad.
They must be used responsibly, with careful planning.

Local-first is a trust model with internal responsibility.
The service and data are on your own hardware.

Mirroring between the services is a viable solution for SMBs.
It enhances resiliency and data security.

Responsibility shifts from vendor to operator.

Leave a comment