Self-Host Gitea — The Deploy Remote Isn't a Code Host

·5 min read·Updated
giteagitself-hosteddockerhow-to

The git remote this platform gives you is a deploy mechanism, not a code host. It's push-only — you can't clone or fetch from it — and it only reacts to two branch names, because its job is to receive your source and build an image. That's exactly right for deploying and useless for keeping your work somewhere. If you want your own place to store repositories, review changes, and file issues, run Gitea as an application: a single Go binary, SQLite on a volume, one HTTPS port. It's one of the better-fitting self-hosted tools, with two configuration details that decide whether it works properly.

What you're actually replacing

Worth being precise, because the two things overlap confusingly:

Deploy remote Gitea
Push code Yes Yes
Clone / fetch No Yes
Branches deploy and build only Any
Issues, reviews, history browsing No Yes
Purpose Trigger a build Store and collaborate on code

They coexist happily. Keep your repository in Gitea, and push to the deploy remote when you want to ship — the same way people use GitHub alongside it.

Deploying it

Use the Existing Image path, which lets you set image, ports, volumes and environment variables at creation time:

  • Image: gitea/gitea
  • Port: container 3000, protocol HTTPS
  • Volume: /data

That's the whole deployment. Everything below is configuration.

ROOT_URL is the setting that matters

Gitea generates clone URLs, redirect targets, and API links from its configured root URL. Get it wrong and the site loads while every clone command it shows you is wrong — a confusing state, because nothing looks broken.

The docs describe ROOT_URL as being there to "overwrite the automatically generated public URL. This is useful if the internal and the external URL don't match (e.g. behind a reverse proxy)" (Gitea docs) — which is exactly the situation here, since TLS is terminated at the edge and your container sees plain HTTP on port 3000.

Gitea reads configuration from environment variables shaped GITEA__<section>__<KEY>, and the official Docker image applies them for you at startup. So set it as an environment variable on the app:

GITEA__server__ROOT_URL=https://your-app-hostname/

You won't know the hostname until the app exists, so create it, read the URL, set the variable, and restart. Same shape as any app that needs to know its own address.

Turn off registration immediately

Your app is on a public URL the moment it starts, and there's no IP filtering or WAF in front of it — protections are yours to add. A fresh Gitea with open sign-ups on a public address will collect accounts that aren't yours.

GITEA__service__DISABLE_REGISTRATION=true

The docs describe it plainly: "Disable registration, after which only admin can create accounts for users."

The sequence that avoids locking yourself out: start with registration enabled, create your own account (the first user becomes an administrator), then set the variable and restart. After that you create any further users from the admin panel.

SSH is the awkward part

Gitea normally offers git over SSH on port 22. You can bind TCP ports here, but the host-side port is derived rather than chosen — a bound TCP port arrives on a high, non-standard number, not 22.

That's workable and fiddly: every clone URL needs an explicit port, and some tooling assumes 22.

The simpler answer is to use HTTPS remotes and skip SSH entirely. Gitea supports access tokens for HTTPS authentication, which are easier to rotate than keys anyway, and your HTTPS port binding already gives you a valid certificate. For a personal or small-team instance this costs you nothing.

Storage and backups

SQLite is Gitea's default and is genuinely fine for a personal instance — one process owns the data, and it's on local disk where SQLite wants to be. Everything lives under /data: the database, the repositories themselves, and uploaded avatars and attachments.

Two consequences to plan for:

  • Repositories grow, and volume size counts against your account's disk limit. Exceeding that limit stops every app you run, so a Gitea instance accumulating large repositories is one of the more plausible ways to get there. Watch the trend.
  • Nothing backs it up. Your code lives in at least two places if you also push to the deploy remote, but issues, pull requests, and settings live only here. Ship a copy off the platform if any of that matters.

Be honest about size

Gitea is heavier than a single-purpose tool like a notification server — it's a web application with a database, a git implementation, and background jobs. Budget for its CPU, RAM, and disk allocation, and start small since you can move up on evidence.

One thing to skip on a small allocation: Gitea Actions. CI runners want their own resources, and running builds beside your git host on a fractional CPU is asking for trouble. If you want CI, run it where CI runs well and keep this instance for storage and review.

FAQ

Can I clone from the platform's git remote?

No. It's push-only and exists to trigger builds. Use a code host — your own Gitea, or a service like GitHub — for storing repositories.

What do I need to run Gitea?

The official image, an HTTPS port binding on container port 3000, and a volume at /data. Configuration comes from GITEA__section__KEY environment variables.

Why are the clone URLs wrong?

ROOT_URL isn't set to your public address. Gitea generates links from it, and it can't infer the external URL from behind TLS termination.

How do I stop strangers signing up?

Set GITEA__service__DISABLE_REGISTRATION=true after creating your own account. The first registered user becomes an administrator.

Can I use git over SSH?

Not on port 22 — bound TCP ports land on a non-standard host port. HTTPS remotes with an access token are simpler.

Do I still need the deploy remote?

Yes, if you deploy by git push. Keep the repository in Gitea and push to the deploy remote to ship.

Wrapping up

Running your own code host used to mean a VM and an afternoon; here it's an image, a volume, and two environment variables. The two that matter are the ones that don't announce themselves — a ROOT_URL that matches your public address, so the URLs Gitea hands out are usable, and registration turned off before someone else finds a fresh instance on a public hostname. Get those right and it's an unremarkable long-running app, sitting alongside the deploy remote rather than replacing it.