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-amd64chmod +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: falsefor local images- Many community actions (including
actions/checkout) require Node.js inside the runner environment. upload-artifact@v4not 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.