Your code, your server, your rules
Why Leave GitHub?
This is not about hating GitHub. GitHub is a great platform. It changed how the world collaborates on code. But the ownership and direction of the platform have changed, and those changes matter if you care about who has access to your work.
Here are the facts:
- Microsoft acquired GitHub in 2018 for $7.5 billion. Since then, GitHub has been integrated deeper into Microsoft's ecosystem.
- GitHub Copilot launched in 2021, trained on public repositories hosted on GitHub. This raised immediate questions about licensing and consent.
- Copilot is now enabled by default on GitHub. As of April 24, 2026, GitHub's default setting allows Copilot to use repository content and user data for AI training and improvement. You can opt out, but the default is opt-in.
- Your private repos live on Microsoft's infrastructure. You are trusting Microsoft's policies, security, and future decisions with your code. Those policies can change at any time.
None of this is conspiracy theory. It is documented in GitHub's own terms of service and Copilot settings pages. You can read the details at GitHub's Copilot policy documentation.
If you are fine with that, no judgment. But if you want to own your code infrastructure the same way you own your home network, read on.
What is Forgejo?
Forgejo is a self-hosted Git forge. Think of it as your own private GitHub that runs on your server. It is a community fork of Gitea, which itself was a fork of Gogs. Forgejo is maintained by the Codeberg community and is fully open source.
What you get:
- Full Git repository hosting with a web interface
- Issue tracking, pull requests, milestones, and labels
- Repository migration tools (imports from GitHub, GitLab, Gitea, etc.)
- User and organization management
- API-compatible with Gitea and partially compatible with the GitHub API
- Lightweight enough to run on minimal hardware
- No telemetry, no tracking, no AI training on your code
What This Guide Covers
- Server prerequisites and setup
- Docker and Docker Compose installation
- UFW firewall configuration
- SSH hardening
- Forgejo installation via Docker Compose
- Initial Forgejo configuration
- Importing repositories from GitHub
- Updating your local Git remotes
- Backup strategy
- Maintenance and upgrades
Prerequisites
Server Requirements
You need a Linux server. This can be a dedicated machine, a virtual machine, or an LXC container. This guide uses Ubuntu 24.04 LTS Server, but any Debian-based distro will work with minor adjustments.
| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 1 vCPU | 2 vCPUs |
| RAM | 2 GB | 4 GB |
| Disk | 20 GB | 40 GB+ |
| OS | Ubuntu 24.04 LTS Server (or similar) | |
This guide assumes you already have a server running with SSH access and sudo privileges. If you need help setting up a VM, see our Proxmox guide for the general VM creation process, or refer to your hosting provider's documentation.
If you are running this inside an LXC container, you must enable the nesting feature on the container. Without it, Docker will not work inside LXC. In the Proxmox web UI: select your container > Options > Features > set nesting=1.
Network Requirements
- A static IP address for your server (set via DHCP reservation or static config)
- Ports 3000 (web UI) and 222 (Git SSH) available on your LAN
- Outbound internet access (for Docker image pulls and system updates)
Throughout this guide, example IPs use 192.168.1.100 for the Forgejo server and 192.168.1.0/24 for the local network. Replace these with your actual values.
On Your Workstation
- Git installed
- SSH key pair (for Git-over-SSH)
- A web browser (for the Forgejo web UI and GitHub token generation)
Step 1: Prepare the Server
Update and Install Essentials
sudo apt update && sudo apt upgrade -y
sudo apt install -y \
ca-certificates \
curl \
gnupg \
lsb-release \
git \
git-lfs \
htop \
net-tools \
unattended-upgrades \
apt-listchanges
Configure Automatic Security Updates
sudo dpkg-reconfigure -plow unattended-upgrades
Select Yes when prompted. Verify with:
cat /etc/apt/apt.conf.d/20auto-upgrades
Expected output:
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
Set Your Timezone
sudo timedatectl set-timezone YOUR_TIMEZONE
Replace YOUR_TIMEZONE with your timezone (e.g., America/New_York, America/Chicago, Europe/London). Verify with timedatectl.
Update /etc/hosts
sudo nano /etc/hosts
Add your server's hostname and IP:
127.0.0.1 localhost
127.0.1.1 forgejo
192.168.1.100 forgejo
Replace 192.168.1.100 with your server's actual IP. Save and exit (Ctrl+O, Enter, Ctrl+X).
Reboot if Kernel Was Updated
sudo reboot
Step 2: Install Docker
Add Docker's Official GPG Key and Repository
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo 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" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Install Docker Engine and Compose
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
Configure Permissions
sudo usermod -aG docker $USER
Log out and back in for the group change to take effect.
Enable Docker on Boot
sudo systemctl enable docker
sudo systemctl enable containerd
Verify
docker --version
docker compose version
docker run hello-world
You should see version info and a "Hello from Docker!" message.
Step 3: Configure Firewall
This is a private server. Only your local network should be able to reach it. No services are exposed to the internet.
Set Default Policies
sudo ufw default deny incoming
sudo ufw default allow outgoing
Allow SSH
sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp comment 'SSH from LAN'
Allow Forgejo Web UI (Port 3000)
sudo ufw allow from 192.168.1.0/24 to any port 3000 proto tcp comment 'Forgejo Web UI from LAN'
Allow Forgejo Git SSH (Port 222)
sudo ufw allow from 192.168.1.0/24 to any port 222 proto tcp comment 'Forgejo Git SSH from LAN'
Enable UFW
sudo ufw enable
Type y to confirm.
Verify
sudo ufw status verbose
You should see all three rules scoped to your LAN subnet. No "Anywhere" rules.
After enabling UFW, open a new terminal and verify you can still SSH in before closing your existing session. If you get locked out, use your hypervisor's console (Proxmox, VMware, etc.) to fix the rules.
Optional: Disable IPv6 in UFW
If you are not using IPv6 on your network:
sudo nano /etc/default/ufw
Change IPV6=yes to IPV6=no, then reload:
sudo ufw reload
Step 4: Harden SSH
Copy Your SSH Key to the Server
From your workstation:
ssh-copy-id YOUR_USERNAME@192.168.1.100
Test key-based login:
ssh YOUR_USERNAME@192.168.1.100
You should connect without a password prompt.
Lock Down the SSH Daemon
Only do this after confirming key-based auth works.
sudo nano /etc/ssh/sshd_config.d/99-hardening.conf
Add:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
AllowUsers YOUR_USERNAME
Replace YOUR_USERNAME with your actual username.
sudo systemctl restart ssh
Test from a new terminal before closing your current session.
Step 5: Install Forgejo
Create the Directory
sudo mkdir -p /opt/forgejo
sudo chown $USER:$USER /opt/forgejo
Create the Docker Compose File
nano /opt/forgejo/docker-compose.yml
Paste the following:
networks:
forgejo:
external: false
services:
server:
image: codeberg.org/forgejo/forgejo:14
container_name: forgejo
environment:
- USER_UID=1000
- USER_GID=1000
- FORGEJO__server__DOMAIN=192.168.1.100
- FORGEJO__server__SSH_DOMAIN=192.168.1.100
- FORGEJO__server__ROOT_URL=http://192.168.1.100:3000/
- FORGEJO__server__SSH_PORT=222
- FORGEJO__server__SSH_LISTEN_PORT=22
restart: always
networks:
- forgejo
volumes:
- ./forgejo-data:/data
- /etc/localtime:/etc/localtime:ro
ports:
- '3000:3000'
- '222:22'
Replace every instance of 192.168.1.100 with your server's actual IP address. Save and exit.
Start Forgejo
cd /opt/forgejo
docker compose up -d
Verify
docker compose ps
You should see a container named forgejo with status Up and ports 0.0.0.0:222->22/tcp and 0.0.0.0:3000->3000/tcp.
Check logs for errors:
docker compose logs -f
Press Ctrl+C to stop following.
Step 6: Configure Forgejo
Open your browser and go to:
http://YOUR_SERVER_IP:3000
You will see the Forgejo initial configuration page. Most defaults are fine since we set environment variables in Docker Compose. Verify the following:
Database Settings
| Setting | Value |
|---|---|
| Database Type | SQLite3 |
| Path | /data/gitea/forgejo.db |
SQLite is more than enough for personal or small-team use. No external database needed.
General Settings
| Setting | Value |
|---|---|
| Site Title | Your choice (e.g., "Forgejo") |
| Server Domain | YOUR_SERVER_IP |
| SSH Server Port | 222 |
| HTTP Listen Port | 3000 |
| Base URL | http://YOUR_SERVER_IP:3000/ |
Administrator Account
Create your admin account with a strong password. The first registered user automatically becomes the administrator.
Click Install Forgejo. After installation, log in with the admin account you just created. You should see a clean dashboard with your profile, an empty repository list, and an activity feed.
Step 7: Import Repositories from GitHub
Generate a GitHub Personal Access Token
You need a temporary token to pull your repos from GitHub.
- Go to github.com/settings/tokens
- Click Generate new token > Generate new token (classic)
- Name it something like
Forgejo Migration - Set expiration to 7 days (you only need it temporarily)
- Select the
reposcope (full control of private repositories) - Click Generate token and copy it immediately
Import a Repository
- In Forgejo, click the + button (top right) > New Migration
- Select GitHub as the source
- Fill in:
- Clone Address:
https://github.com/yourusername/your-repo.git - Access Token: Paste your GitHub token
- Repository Name: Same as original (or rename)
- Migration Items: Check all that apply (Topics, Milestones, Labels, Issues, Pull Requests, Releases)
- Clone Address:
- Click Migrate Repository
Repeat for each repository you want to migrate.
Verify Each Import
- Commit history is complete
- All branches are present
- Files are intact
- Issues and labels imported correctly (if applicable)
After migration is complete, go back to GitHub and delete the personal access token. You do not need it anymore and it should not be left active.
Step 8: Update Your Local Git Remotes
After importing your repos to Forgejo, point your local clones to the new server.
Check Your Current Remote
cd /path/to/your/repo
git remote -v
You will see something like:
origin https://github.com/yourusername/repo.git (fetch)
origin https://github.com/yourusername/repo.git (push)
Update the Remote
Option A: HTTP (simpler, uses password/token auth)
git remote set-url origin http://192.168.1.100:3000/yourusername/repo.git
Option B: SSH (uses SSH key auth, recommended)
git remote set-url origin ssh://git@192.168.1.100:222/yourusername/repo.git
Add Your SSH Key to Forgejo (for SSH)
- Copy your public key:
cat ~/.ssh/id_ed25519.pub(orid_rsa.pub) - In Forgejo, go to Settings > SSH / GPG Keys
- Click Add Key, paste your key, give it a name
- Click Add Key
Verify
git remote -v
git fetch
git push
Everything should work the same as it did with GitHub.
Backup Strategy
What to Back Up
The Forgejo data directory contains everything:
/opt/forgejo/forgejo-data/
This includes all Git repositories, the SQLite database, configuration files, and any uploaded assets.
Application-Level Backup
Create a backup script:
nano /opt/forgejo/backup.sh
#!/bin/bash
# Forgejo Backup Script
BACKUP_DIR="/opt/forgejo/backups"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
# Use Forgejo's built-in dump command
docker exec forgejo /usr/local/bin/forgejo dump \
-c /data/gitea/conf/app.ini \
--file "/data/forgejo-dump-${DATE}.zip"
# Move the dump out of the container volume
mv /opt/forgejo/forgejo-data/forgejo-dump-${DATE}.zip "$BACKUP_DIR/"
# Clean up backups older than 30 days
find "$BACKUP_DIR" -name "forgejo-dump-*.zip" -mtime +30 -delete
echo "Backup complete: $BACKUP_DIR/forgejo-dump-${DATE}.zip"
Make it executable and schedule it:
chmod +x /opt/forgejo/backup.sh
Add a daily cron job (runs at 2:00 AM):
crontab -e
Add this line:
0 2 * * * /opt/forgejo/backup.sh >> /var/log/forgejo-backup.log 2>&1
If your server runs on a hypervisor like Proxmox with a guest agent installed, you can also use hypervisor-level backups for full VM snapshots with filesystem consistency.
Maintenance and Upgrades
Updating Forgejo
The :14 tag in your compose file tracks the latest patch release in the v14.x line. To update:
cd /opt/forgejo
docker compose pull
docker compose down
docker compose up -d
Verify the update by checking the version in the startup logs:
docker compose logs | head -20
Major Version Upgrades
Major upgrades (e.g., v14 to v15) require a manual version bump:
- Read the release notes at forgejo.org/releases
- Back up your data first
- Update the image tag in
docker-compose.yml(e.g., change:14to:15) - Pull and restart with the commands above
- Check logs for migration messages and verify the web UI
Updating the Host OS
sudo apt update && sudo apt upgrade -y
Reboot if a kernel update was applied. Docker and Forgejo will restart automatically thanks to the restart: always policy.
Troubleshooting
Forgejo Will Not Start
cd /opt/forgejo
docker compose logs -f
Common issues:
- Port conflict: Another service is using port 3000 or 222. Check with
sudo ss -tlnp | grep -E '3000|222' - Permission issue: The
forgejo-datadirectory must be owned by UID/GID 1000. Fix withsudo chown -R 1000:1000 /opt/forgejo/forgejo-data - Disk full: Check with
df -h
Cannot Access Web UI
- Verify container is running:
docker compose ps - Verify firewall:
sudo ufw status - Test from the server itself:
curl http://localhost:3000 - Check if port is listening:
sudo ss -tlnp | grep 3000
Cannot Push or Pull via SSH
- Verify port 222 is open:
sudo ss -tlnp | grep 222 - Test connectivity:
ssh -T git@YOUR_SERVER_IP -p 222 - Verify your SSH key is added in Forgejo's web UI under Settings > SSH / GPG Keys
- Check the URL format:
ssh://git@YOUR_SERVER_IP:222/username/repo.git
Cannot Push or Pull via HTTP
- Verify remote URL:
git remote -v - Test with:
git ls-remote http://YOUR_SERVER_IP:3000/username/repo.git - You may need an access token for HTTP auth: go to Settings > Applications > Generate New Token
Quick Reference
Service Management
| Action | Command |
|---|---|
| Start | cd /opt/forgejo && docker compose up -d |
| Stop | cd /opt/forgejo && docker compose down |
| Restart | cd /opt/forgejo && docker compose restart |
| Logs | cd /opt/forgejo && docker compose logs -f |
| Update | cd /opt/forgejo && docker compose pull && docker compose down && docker compose up -d |
| Status | cd /opt/forgejo && docker compose ps |
Key Paths
| Path | Purpose |
|---|---|
/opt/forgejo/ | Project root |
/opt/forgejo/docker-compose.yml | Docker Compose config |
/opt/forgejo/forgejo-data/ | All data (repos, DB, config) |
/opt/forgejo/backups/ | Application backups |
Git Remote Formats
| Protocol | Format |
|---|---|
| HTTP | http://YOUR_SERVER_IP:3000/username/repo.git |
| SSH | ssh://git@YOUR_SERVER_IP:222/username/repo.git |