How to Install Gitea on Ubuntu 24.04 — Self-Hosted Git Server in 30 Minutes
Running your own Git server with Gitea gives you unlimited private repositories, full data sovereignty, and a developer experience that rivals GitHub — without per-seat fees, storage caps, or vendor lock-in. This tutorial walks through installing Gitea on Ubuntu 24.04 from first SSH login to a production deployment with PostgreSQL, systemd, Nginx TLS, Gitea Actions-ready configuration, Package Registry, and Git LFS.
Want a bigger DevOps stack? If you need an integrated container registry, Kubernetes integration, and built-in security scanning, compare this guide with our GitLab install guide before choosing.
Table of Contents
What is Gitea?
Gitea is a lightweight, self-hosted Git service written in Go. It ships as a single static binary with no runtime dependencies — no Ruby on Rails, no Elixir, no JVM — and runs comfortably on a 2 GB VPS while serving dozens of developers. Despite the small footprint, Gitea offers a feature set that compares directly with GitHub or GitLab for day-to-day development work.
Out of the box, Gitea provides Git repositories with branches, tags, and releases; pull requests with reviews and merge rules; issues with labels, milestones, and project boards; per-repo wikis; webhooks to trigger external systems; and a full REST and GraphQL-style API. It supports multiple authentication sources including local accounts, LDAP, SMTP, PAM, OAuth2, OpenID Connect, and SAML. Organizations and teams map cleanly to real-world company structures, with fine-grained permissions per repository.
More recent Gitea releases have added significant platform features. Gitea Actions, introduced in 1.19 and stabilized through 1.22, brings GitHub Actions-compatible CI/CD with a YAML workflow syntax that runs many existing Action marketplace modules unchanged. The Package Registry accepts uploads for npm, Docker, Maven, PyPI, NuGet, RubyGems, Composer, Conan, Helm, Cargo, Alpine, Debian, RPM, and Generic formats — a single binary replaces multiple specialised artefact stores. Git LFS is built in, letting you track large binaries without external services. Mirror sync keeps repositories in lockstep with upstream GitHub, GitLab, or Bitbucket copies in either direction.
Typical deployments span a wide range: indie developers self-hosting personal projects on a 2 GB VPS, startups running engineering for 10-30 developers on a 4 GB VPS, agencies hosting client work with strict data-residency requirements, and educational institutions providing per-student Git accounts behind their own SSO. Because Gitea is MIT-licensed, there are no feature gates, seat caps, or enterprise paywalls — everything in the binary is free forever.
Why Self-Host Git Instead of Using GitHub?
GitHub is excellent for public open source work and well suited to most teams, but self-hosting Git on your own VPS unlocks advantages that matter for businesses, agencies, and privacy-conscious developers.
- Unlimited private repositories — GitHub's free tier is generous but has limits on Actions minutes, package storage, and Codespaces hours. On your own Gitea server, every repository is private by default, every build runs on hardware you rent, and you never hit surprise metering.
- Data sovereignty — Source code is one of the most sensitive assets a software company owns. Keeping it on a server in a jurisdiction you control simplifies GDPR compliance, export-control rules, customer contracts that forbid third-party cloud processors, and government procurement requirements.
- No vendor lock-in — Gitea stores everything on disk: bare Git repositories under
/var/lib/gitea/repositories/, attachments and LFS objects alongside them, and metadata in PostgreSQL. Migrating to Forgejo or any other Git server is a file copy plus a database dump. - Predictable flat-rate cost — A 2 GB VPS at around EUR 7.99/month hosts unlimited users, repositories, and CI runners. GitHub Team at $4/user/month plus Actions minutes scales linearly with team size; Gitea does not.
- Full customization — Add your own OAuth2 provider, integrate with internal SSO, enforce custom commit-message policies, run custom CI runners with GPUs, expose the API to internal tooling — you own the stack end to end.
- Offline and air-gapped capability — For regulated industries (defence, medical devices, finance), Gitea runs entirely inside a private network with no outbound calls required.
- Performance — Self-hosted Git operations use LAN bandwidth between your CI runners and the Git server. Clones and pushes that take 30 seconds against GitHub over the public internet complete in under 5 seconds on the same VPS LAN.
Cost Comparison: GitHub vs. Self-Hosted Gitea
| Scenario | GitHub Free | GitHub Team | Self-Hosted Gitea (Starter VPS) |
|---|---|---|---|
| Monthly cost | Free (with limits) | $4/user/month | EUR 7.99/mo (unlimited users) |
| Private repositories | Unlimited | Unlimited | Unlimited |
| CI/CD minutes | 2,000/mo | 3,000/mo | Unlimited (on your hardware) |
| Package storage | 500 MB | 2 GB | Limited only by disk |
| Data location | US/global (GitHub) | US/global (GitHub) | Your chosen DC |
| Cost for 10 devs | $0 (with caps) | $40/mo + overages | EUR 7.99/mo flat |
| Cost for 30 devs | N/A (overages hit) | $120/mo + overages | EUR 7.99/mo flat |
Prerequisites
Before starting, make sure you have:
- A VPS running Ubuntu 24.04 LTS with root or sudo access
- At least 2 GB of RAM and 20 GB of disk space
- A domain name (for example
git.example.com) with an A record pointing to your VPS IP - SSH access to the server
- Ports 22, 80, 443 open in your firewall (and port 2222 if you run Gitea SSH on an alternate port)
Recommended Plan: Starter VPS>
For teams up to around 20 developers, the Starter VPS plan is the right fit:>
- 2 vCPU cores
- 2 GB RAM
- 40 GB NVMe SSD
- Unmetered bandwidth
- EUR 7.99/month>
For larger teams or heavy Gitea Actions workloads, step up to a Professional plan. Gitea's resource needs scale gently — most installs never need more than 4 GB of RAM.
Connect to your server via SSH:
ssh root@your-server-ipStep 1: Update System and Install Prerequisites
Refresh the package index and install Git plus a handful of utilities Gitea relies on.
sudo apt update && sudo apt upgrade -y
sudo apt install -y git curl wget ca-certificates gnupg lsb-releaseVerify Git is at least version 2.34 (Ubuntu 24.04 ships with 2.43+):
git --versionExpected output:
git version 2.43.0If your kernel was updated by the apt upgrade, reboot before continuing:
sudo rebootStep 2: Install and Configure PostgreSQL
Gitea supports SQLite, MySQL, MariaDB, and PostgreSQL. For anything beyond a personal test setup, PostgreSQL is the recommended backend — it handles concurrent writes better, gives you point-in-time recovery, and pairs well with modern backup tooling.
Install PostgreSQL 16 from the default Ubuntu 24.04 repositories:
sudo apt install -y postgresql postgresql-contrib
sudo systemctl enable --now postgresqlCheck the service is running:
sudo systemctl status postgresqlCreate a dedicated database and user for Gitea:
sudo -u postgres psql <<EOF
CREATE ROLE gitea WITH LOGIN PASSWORD 'change-me-to-a-long-random-string';
CREATE DATABASE gitea WITH OWNER gitea TEMPLATE template0 ENCODING 'UTF8' LC_COLLATE 'C' LC_CTYPE 'C';
GRANT ALL PRIVILEGES ON DATABASE gitea TO gitea;
EOFSecurity note: Replacechange-me-to-a-long-random-stringwith a real password. Generate one withopenssl rand -base64 32. Save it in your password manager — you'll paste it intoapp.iniand into the install wizard.
Confirm you can connect as the new user:
psql "host=127.0.0.1 user=gitea password=change-me-to-a-long-random-string dbname=gitea" -c '\conninfo'Expected output:
You are connected to database "gitea" as user "gitea" on host "127.0.0.1" at port "5432".Step 3: Create the git System User
Gitea should run under a dedicated non-login user. This isolates repository files from other services and follows Linux security best practice.
sudo adduser \
--system \
--shell /bin/bash \
--gecos 'Git Version Control' \
--group \
--disabled-password \
--home /home/git \
gitThe user owns its own home directory at /home/git, which is where SSH keys and the server's known_hosts live. The shell is set to /bin/bash (not /bin/false) because Gitea rewrites SSH shell invocations when users push over SSH.
Verify the user exists:
id gitExpected output:
uid=999(git) gid=999(git) groups=999(git)Step 4: Download the Gitea Binary
Gitea ships as a single static binary. Grab the latest stable release from dl.gitea.com. At time of writing, 1.22 is current; always check docs.gitea.com for the newest tag.
GITEA_VERSION=1.22.3
wget -O /tmp/gitea "https://dl.gitea.com/gitea/${GITEA_VERSION}/gitea-${GITEA_VERSION}-linux-amd64"
sudo mv /tmp/gitea /usr/local/bin/gitea
sudo chmod +x /usr/local/bin/gitea
sudo chown root:root /usr/local/bin/giteaVerify the install:
gitea --versionExpected output:
Gitea version 1.22.3 built with GNU Make 4.3, go1.22.7 : bindata, sqlite, sqlite_unlock_notifyStep 5: Create Directories and app.ini
Create the directory layout Gitea expects, with correct ownership.
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 -p /etc/gitea sudo chown root:git /etc/gitea sudo chmod 770 /etc/gitea
Create the main configuration file at /etc/gitea/app.ini:
sudo tee /etc/gitea/app.ini > /dev/null <<'EOF' APP_NAME = My Team Git RUN_USER = git RUN_MODE = prod[server] DOMAIN = git.example.com HTTP_PORT = 3000 ROOT_URL = https://git.example.com/ DISABLE_SSH = false SSH_PORT = 2222 SSH_LISTEN_PORT = 2222 START_SSH_SERVER = true LFS_START_SERVER = true LFS_JWT_SECRET = REPLACE_WITH_LFS_JWT OFFLINE_MODE = false
[database] DB_TYPE = postgres HOST = 127.0.0.1:5432 NAME = gitea USER = gitea PASSWD = change-me-to-a-long-random-string SSL_MODE = disable
[repository] ROOT = /var/lib/gitea/data/gitea-repositories
[lfs] PATH = /var/lib/gitea/data/lfs
[session] PROVIDER = file
[picture] AVATAR_UPLOAD_PATH = /var/lib/gitea/data/avatars REPOSITORY_AVATAR_UPLOAD_PATH = /var/lib/gitea/data/repo-avatars
[attachment] PATH = /var/lib/gitea/data/attachments
[log] MODE = file LEVEL = info ROOT_PATH = /var/lib/gitea/log
[security] INSTALL_LOCK = false SECRET_KEY = REPLACE_WITH_SECRET_KEY INTERNAL_TOKEN = REPLACE_WITH_INTERNAL_TOKEN
[service] DISABLE_REGISTRATION = true REQUIRE_SIGNIN_VIEW = true DEFAULT_KEEP_EMAIL_PRIVATE = true DEFAULT_ALLOW_CREATE_ORGANIZATION = true DEFAULT_ENABLE_TIMETRACKING = true
[actions] ENABLED = true
[packages] ENABLED = true
[mailer] ENABLED = false EOF
Key settings explained
DOMAIN/ROOT_URL— Must match the public URL users will visit. Clone URLs (https://git.example.com/user/repo.git) and webhook signatures are derived fromROOT_URL.SSH_PORT/SSH_LISTEN_PORT = 2222— Runs Gitea's built-in SSH server on a non-standard port so it doesn't clash with the host's OpenSSH on port 22. Users will clone viagit clone ssh://[email protected]:2222/user/repo.git. If you prefer, setSTART_SSH_SERVER = falseand let OpenSSH handle Git traffic on port 22.LFS_START_SERVER = true— Enables the built-in Git LFS server. Configure clients withgit lfs installand push large files alongside code.DISABLE_REGISTRATION = true— Locks down signup so only admins can invite users. Flip tofalsefor community installs.[actions] ENABLED = true— Turns on Gitea Actions so you can register CI runners later.
sudo -u git gitea generate secret SECRET_KEY
sudo -u git gitea generate secret INTERNAL_TOKEN
sudo -u git gitea generate secret LFS_JWT_SECRETEach command prints a long random string. Paste them into /etc/gitea/app.ini replacing REPLACE_WITH_SECRET_KEY, REPLACE_WITH_INTERNAL_TOKEN, and REPLACE_WITH_LFS_JWT. Then tighten permissions:
sudo chmod 640 /etc/gitea/app.ini
sudo chown root:git /etc/gitea/app.iniStep 6: Create the systemd Unit
A systemd service makes Gitea start on boot and restart automatically if it crashes.
sudo tee /etc/systemd/system/gitea.service > /dev/null <<'EOF' [Unit] Description=Gitea (Git with a cup of tea) After=syslog.target After=network.target After=postgresql.service Requires=postgresql.service[Service] RestartSec=2s Type=simple User=git Group=git WorkingDirectory=/var/lib/gitea/ ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini Restart=always Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
Hardening
NoNewPrivileges=true ProtectSystem=strict ReadWritePaths=/var/lib/gitea /etc/gitea PrivateTmp=true PrivateDevices=true ProtectHome=false ProtectKernelTunables=true ProtectControlGroups=true
[Install] WantedBy=multi-user.target EOF
Reload systemd, enable the unit on boot, and start it:
sudo systemctl daemon-reload
sudo systemctl enable --now giteaCheck it came up cleanly:
sudo systemctl status giteaExpected output:
● gitea.service - Gitea (Git with a cup of tea)
Loaded: loaded (/etc/systemd/system/gitea.service; enabled; preset: enabled)
Active: active (running) since Thu 2026-04-16 10:00:00 UTC; 5s ago
Main PID: 4567 (gitea)
Tasks: 9 (limit: 4590)
Memory: 142.3M
CPU: 1.1sConfirm it's listening on port 3000:
sudo ss -tlnp | grep 3000Step 7: Run the First-Install Wizard
Even though app.ini already contains the database credentials, Gitea presents a first-run wizard at http://your-server-ip:3000/install so you can confirm settings and create the initial admin account.
Open your browser to http://your-server-ip:3000. You should see the install screen. Because you pre-filled app.ini, most fields are already populated — just confirm the database section connects and scroll to Administrator Account Settings:
- Administrator Username — for example
admin(avoidroot) - Password — at least 12 characters, stored in your password manager
- Email — a monitored address; password resets and notifications go here
INSTALL_LOCK = true in app.ini, which prevents anyone else from re-running the wizard.Confirm the install lock:
sudo grep INSTALL_LOCK /etc/gitea/app.iniExpected output:
INSTALL_LOCK = trueLog in with the admin account. You now have a working Gitea instance — time to secure it and use it.
Step 8: Add an SSH Key and Push Your First Repo
Adding an SSH key lets you clone, pull, and push without entering a password.
On your local machine, copy your public key:
cat ~/.ssh/id_ed25519.pubIn the Gitea web UI, click your avatar → Settings → SSH / GPG Keys → Add Key. Paste the key, give it a name (for example laptop-2026), and save.
Create a repository from the web UI: + → New Repository → name hello-gitea, tick Initialize Repository.
Clone it on your laptop. Because we moved Gitea SSH to port 2222, use the full SSH URL:
git clone ssh://[email protected]:2222/admin/hello-gitea.git
cd hello-gitea
echo "# Hello Gitea" > README.md
git add README.md
git commit -m "First self-hosted commit"
git push origin mainIf you prefer HTTPS, use a Gitea access token instead of your password:
git clone https://git.example.com/admin/hello-gitea.gitStep 9: Repositories, Forks, Wikis, Issues
Gitea's developer experience mirrors GitHub closely.
Repositories support branches, tags, releases, and protected branches. Click Settings on a repository to configure merge styles (merge commit, rebase, squash), required reviewers, required status checks from Gitea Actions, and push rules like commit-message regex enforcement.
Forks — any user with read access can click Fork to create a personal copy. Contributors push to their fork and open a pull request against the upstream repository, mirroring the GitHub workflow exactly. Admins can enable AllowOnlyContributorsToTrackTime and similar governance toggles under organization settings.
Wikis are stored as a second Git repository alongside the main one. Every page is a markdown file you can clone, edit in your local editor, and push back. Sidebar navigation is auto-generated from the _Sidebar.md file.
Issues support labels, milestones, assignees, due dates, reactions, and reference links between repositories (#123 resolves to the current repo, owner/repo#123 crosses boundaries). Combine issues with Projects (Kanban-style boards) for sprint planning. Issue templates live in .gitea/ISSUE_TEMPLATE/ inside the repo, and pull request templates live at .gitea/PULL_REQUEST_TEMPLATE.md.
For teams coming from GitHub, the mental model is identical — only the domain name changes.
Step 10: Package Registry (npm, Docker, Generic)
Gitea's Package Registry is one of its most underrated features. Instead of running separate Verdaccio, Harbor, and Artifactory services, one Gitea install handles every major package format.
You already enabled it in app.ini with [packages] ENABLED = true. Packages live under /var/lib/gitea/data/packages/ and are visible at the Packages tab on each user or organization page.
Publishing an npm package
npm config set registry https://git.example.com/api/packages/your-org/npm/
npm config set //git.example.com/api/packages/your-org/npm/:_authToken YOUR_GITEA_TOKEN
npm publishTeammates install with:
npm install @your-org/your-package --registry https://git.example.com/api/packages/your-org/npm/Pushing a Docker image
docker login git.example.com -u admin
docker tag myapp:latest git.example.com/your-org/myapp:latest
docker push git.example.com/your-org/myapp:latestDocker images are addressable by both tag and digest, support multi-arch manifests, and integrate with pull secrets in Kubernetes.
Uploading a Generic artefact
The Generic registry accepts any file — ideal for build artefacts like installers, firmware, or release bundles.
curl --user admin:TOKEN \
--upload-file ./my-installer.exe \
https://git.example.com/api/packages/your-org/generic/my-product/1.0.0/my-installer.exeOther formats work the same way: Maven (mvn deploy), PyPI (twine upload), NuGet (dotnet nuget push), RubyGems (gem push), Composer, Conan, Helm, Cargo, and OS packages (Alpine, Debian, RPM). Every format has a /api/packages/<owner>/<format>/... endpoint documented in the Gitea docs.
Step 11: Git LFS Setup
Large binary files — design assets, ML model weights, video — bloat Git history badly. Git LFS replaces the file content with a pointer in the commit and stores the real bytes on the server separately.
You already enabled LFS via LFS_START_SERVER = true in app.ini. On the client:
# Install the LFS client once per machine
git lfs installInside your repo, tell LFS which file patterns to track
git lfs track "*.psd"
git lfs track "*.mp4"
git lfs track "models/*.bin"Commit the .gitattributes file LFS creates
git add .gitattributes
git commit -m "Track large assets with LFS"Push as normal — LFS handles the upload transparently
git pushLFS objects land in /var/lib/gitea/data/lfs/. Each one is content-addressable, so identical files across branches and forks are stored once. For teams that push multi-gigabyte assets frequently, run gitea maintenance commands to audit usage:
sudo -u git gitea -c /etc/gitea/app.ini manager flush-queuesStep 12: Nginx Reverse Proxy with TLS
Exposing Gitea directly on port 3000 works, but you'll want TLS on port 443 and a clean https://git.example.com URL. Nginx handles both.
Install Nginx and Certbot:
sudo apt install -y nginx certbot python3-certbot-nginxCreate the Gitea vhost:
sudo tee /etc/nginx/sites-available/gitea > /dev/null <<'EOF' server { listen 80; server_name git.example.com; return 301 https://$host$request_uri; }server { listen 443 ssl http2; server_name git.example.com;
ssl_certificate /etc/letsencrypt/live/git.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/git.example.com/privkey.pem;
# Raise upload size for LFS pushes and large commits client_max_body_size 512m;
# Security headers add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options SAMEORIGIN always; add_header Referrer-Policy strict-origin-when-cross-origin always;
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;
# Long timeouts for large git pushes proxy_read_timeout 600s; proxy_send_timeout 600s; } } EOF
sudo ln -s /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/ sudo nginx -t
Obtain the TLS certificate:
sudo certbot --nginx -d git.example.comCertbot edits the vhost, installs the certificate, and sets up a renewal timer. Reload Nginx:
sudo systemctl reload nginxUpdate app.ini so Gitea knows it's behind TLS:
[server]
DOMAIN = git.example.com
ROOT_URL = https://git.example.com/Restart Gitea:
sudo systemctl restart giteaOpen https://git.example.com — you should see the Gitea dashboard served over HTTPS with a valid certificate.
Finally, open your firewall for ports 80, 443, and 2222 (the Gitea SSH port):
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 2222/tcp
sudo ufw enableStep 13: Backups with gitea dump
Gitea ships with a backup command that packages everything into a single zip file: database, repositories, LFS objects, attachments, avatars, and app.ini.
Create a backup directory and run a dump:
sudo mkdir -p /var/backups/gitea sudo chown git:git /var/backups/gitea
sudo -u git gitea -c /etc/gitea/app.ini dump \ -f /var/backups/gitea/gitea-dump-$(date +%F).zip
Schedule it nightly via cron. Edit the git user's crontab:
sudo crontab -u git -eAdd:
0 2 /usr/local/bin/gitea -c /etc/gitea/app.ini dump -f /var/backups/gitea/gitea-dump-$(date +\%F).zip && find /var/backups/gitea -name 'gitea-dump-.zip' -mtime +7 -deleteThis runs at 02:00 every night and prunes dumps older than seven days. For production, copy each night's zip off-server to S3, Backblaze B2, or another VPS using rclone or restic — a local-only backup doesn't help if the VPS itself is lost.
To restore, extract the zip, import the SQL with psql, and copy the repos/ directory back into place. Full restore steps are documented at docs.gitea.com.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
502 Bad Gateway from Nginx | Gitea not running or wrong upstream port | sudo systemctl status gitea and verify proxy_pass matches HTTP_PORT in app.ini |
| Clone over SSH hangs | Built-in SSH server port blocked by firewall | sudo ufw allow 2222/tcp or disable the built-in server and use OpenSSH on 22 |
fatal: Authentication failed on HTTPS push | GitHub-style password auth disabled | Create a personal access token under Settings → Applications and use it as the password |
| LFS push fails with 413 | Nginx body size too small | Raise client_max_body_size in the vhost to 512m or higher, reload Nginx |
pq: role "gitea" does not exist in logs | Postgres user not created or wrong password | Re-run the CREATE ROLE statement and update PASSWD in app.ini |
| Webhook deliveries failing | Gitea can't resolve its own ROOT_URL | Check DNS resolves from the server, check outbound firewall rules |
High disk usage under /var/lib/gitea/data/lfs | Dangling LFS objects from deleted repos | Run gitea doctor and then gitea manager flush-queues |
Viewing logs
sudo journalctl -u gitea -f
sudo tail -f /var/lib/gitea/log/gitea.logFAQ
Is Gitea really free and open source?
Yes. Gitea is MIT-licensed, fully open source, and has no paid tier or feature gating. Every feature — unlimited private repos, CI/CD with Gitea Actions, Package Registry, LFS, wikis, issues, pull requests — is available in the free community edition. Gitea Enterprise exists as a paid offering with additional support and SLAs, but the software itself is identical to the community release.
How much RAM does Gitea need?
Gitea is remarkably lightweight compared to GitLab. A small team of 5-20 developers runs comfortably on 2 GB RAM with PostgreSQL on the same server. Gitea itself typically uses 150-300 MB of RAM at rest, rising modestly during heavy Git operations (pack file creation on large clones, Git LFS ingestion). The Starter VPS plan with 2 GB RAM is a good fit for most self-hosted teams.
Can Gitea replace GitHub for a small team?
For most small and medium teams, yes. Gitea provides repositories, forks, pull requests, issues, wikis, projects, releases, a built-in package registry (npm, Docker, Maven, PyPI, NuGet, Generic), LFS, webhooks, SSO via OAuth2/OIDC, and CI/CD via Gitea Actions. What it lacks are GitHub Copilot, GitHub Codespaces, and the massive public marketplace — but for private team development, Gitea covers the essentials.
How does Gitea compare to GitLab and Forgejo?
Gitea is the lightweight self-hosted Git server — single Go binary, minimal resource usage, fast to install. GitLab is a full DevOps platform with built-in CI runners, container registry, Kubernetes integration, and security scanning, but it needs 8 GB+ RAM and is complex to operate. Forgejo is a community fork of Gitea that tracks Gitea closely but is governed by a non-profit; migrations between the two are trivial because the data formats are identical. Choose Gitea for a simple, fast, low-resource Git server; choose GitLab if you want an integrated DevOps suite; choose Forgejo if you prefer non-profit governance.
How do I migrate repositories from GitHub to Gitea?
Gitea has a built-in migration tool that imports from GitHub, GitLab, Bitbucket, and raw Git URLs. From the web UI, click the plus icon and choose Migration, then select GitHub and paste a personal access token with repo scope. Gitea will copy the code, issues, pull requests, labels, milestones, and releases. For bulk migration of many repos, use the Gitea API or the gitea-github-migrator tool. LFS objects migrate automatically when both sides support LFS.
Is Gitea Actions fully compatible with GitHub Actions?
Gitea Actions uses the same YAML syntax as GitHub Actions and runs many actions from the GitHub marketplace unchanged. Core actions like actions/checkout, actions/setup-node, and actions/cache work out of the box. The runner is a fork of nektos/act. Differences exist around GitHub-specific services (GitHub Packages, OIDC with cloud providers, deployment environments), but for build-test-deploy pipelines the compatibility is excellent. If you're coming from a different CI background, compare with Drone CI which offers a similar self-hosted model with a different YAML dialect.
How do I back up Gitea safely?
Use the built-in gitea dump command, which captures the database, repositories, LFS objects, attachments, avatars, and configuration into a single zip file. Run it as the git user on a nightly cron schedule and ship the zip off-server to S3, Backblaze B2, or another VPS via rclone or restic. For large instances (100 GB+ of repos), use pg_dump for the database plus filesystem snapshots of the repository directory to avoid long single-process dumps.
Next Steps
Your Gitea server is running in production. Here's where to go from here:
- Set up Gitea Actions runners — Follow the Gitea Actions runner setup guide to register a CI runner and start running GitHub Actions-compatible workflows on every push.
- Compare with alternative CI — If you prefer a lighter, container-native CI, see our Drone CI install guide. Drone integrates with Gitea via OAuth and runs pipelines in ephemeral Docker containers.
- Evaluate Forgejo — If you're interested in community governance, read our Forgejo install guide. Migrations between Gitea and Forgejo are trivial since they share a common ancestor.
- Consider GitLab for bigger teams — If your needs grow beyond Git hosting and CI — container registry, Kubernetes auto-deploy, built-in security scanning — the GitLab install guide covers the full DevOps platform.
- Hook up monitoring — Point Uptime Kuma or a Prometheus blackbox exporter at
https://git.example.com/api/healthzto get alerts before users notice downtime.
- Read the docs — The official docs.gitea.com is excellent. Bookmark the Administration and API sections.
Need a VPS to host Gitea?>
Our Starter VPS plan gives you 2 vCPU, 2 GB RAM, and 40 GB NVMe SSD for EUR 7.99/month — more than enough for a team of 10-20 developers running Gitea, PostgreSQL, and Nginx together.>
- Full root access on Ubuntu 24.04 LTS
- Unmetered bandwidth
- Deploy in under 60 seconds
- Scale up to more RAM without rebuilding when your team grows>
Launch your Git server VPS now.