Gitea RPM Package
Self-hosted Git service packaged as an RPM for Fedora. Ships as a base package
with two mutually exclusive subpackages: gitea-service (native binary) and
gitea-container (Podman quadlet with rootless image).
Package structure
gitea Base: git user (UID 124), dirs, /etc/gitea/app.ini
gitea-service Native binary under systemd
gitea-container Podman quadlet (gitea + postgres containers in a pod)
Path resolution and environment variables
Gitea resolves its key paths through a priority chain. Understanding this is
essential because the native install, rootful Docker, and rootless Docker each
set these differently. Run gitea --help to see the computed defaults:
DEFAULT CONFIGURATION:
AppPath: /usr/libexec/gitea # absolute path to binary
WorkPath: /var/lib/gitea # resolved AppWorkPath
CustomPath: /var/lib/gitea/custom # {WorkPath}/custom
ConfigFile: /var/lib/gitea/custom/conf/app.ini # {CustomPath}/conf/app.ini
(When run without overrides, WorkPath defaults to the binary's directory.)
AppPath
Absolute path to the Gitea binary itself. Not configurable at runtime — it is always the real path of the running executable. All other paths that are relative get resolved against the directory of AppPath.
AppWorkPath (WORK_PATH)
The working directory that serves as the base for all relative paths. Resolved in this order (first wins):
--work-path/-wCLI flagGITEA_WORK_DIRenvironment variableWORK_PATHinapp.ini- Built-in value (set at compile time via LDFLAGS)
- Directory containing AppPath (the binary)
If any of the above are relative, they become absolute against AppPath's directory.
| Deployment | How it's set | Effective value |
|---|---|---|
| Gitea default | Falls through to rule 5 (binary dir) | (binary dir) |
| Native install (RPM) | Environment=GITEA_WORK_DIR=/var/lib/gitea in unit |
/var/lib/gitea |
| Docker rootful | Built into image (LDFLAGS at compile time) | /data |
| Docker rootless | Built into image (LDFLAGS at compile time) | /data |
CustomPath (GITEA_CUSTOM)
Base directory for custom templates, public files, and (by default) app.ini.
Resolved in this order:
--custom-path/-CCLI flagGITEA_CUSTOMenvironment variable- Built-in value (compile time)
{AppWorkPath}/custom
CustomConf (app.ini location)
--config/-cCLI flag- Built-in value (compile time)
{CustomPath}/conf/app.ini
Note: this RPM uses -c /etc/gitea/app.ini in the systemd unit to override
the default {CustomPath}/conf/app.ini path.
APP_DATA_PATH
Defined in [server]. Defaults to {AppWorkPath}/data (or /data/gitea on
rootful Docker). All relative paths in storage-related settings resolve against
this. Serves as root for: LFS, avatars, packages, attachments, queues,
indexers, etc.
HOME directory and RUN_USER
RUN_USER in app.ini specifies which OS user Gitea expects to run as
(default: git). Gitea uses ~ / HOME for:
SSH_ROOT_PATHdefaults to~/.sshSSH_SERVER_HOST_KEYdefaults to~/.ssh/id_rsaauthorized_keysmanagement (whenSSH_CREATE_AUTHORIZED_KEYS_FILE = true)
| Deployment | RUN_USER | HOME |
|---|---|---|
| Native install (RPM) | git |
/var/lib/gitea |
| Docker rootful | git |
/data/gitea |
| Docker rootless | git |
/var/lib/gitea |
Docker-specific environment variables
| Variable | Purpose |
|---|---|
USER |
Username inside container (default: git) |
USER_UID |
Unix UID, must match volume ownership (default: 1000) |
USER_GID |
Unix GID, must match volume ownership (default: 1000) |
GITEA__section__KEY |
Override any app.ini value (e.g. GITEA__database__HOST=db:5432) |
GITEA__section__KEY__FILE |
Same but reads value from a file path (for secrets) |
This RPM sets USER_UID=124 / USER_GID=124 in the quadlet to match the
system git user created by sysusers.d.
Directory layout
Gitea default (built-in, relative to WORK_PATH)
When Gitea runs without any overrides, all paths are relative to the working
directory (typically where the binary lives or GITEA_WORK_DIR).
WORK_PATH/
custom/ # custom templates, public files
data/ # APP_DATA_PATH
gitea-repositories/ # [repository] ROOT
lfs/ # [lfs] PATH
avatars/ # [picture] AVATAR_UPLOAD_PATH
repo-avatars/ # [picture] REPOSITORY_AVATAR_UPLOAD_PATH
packages/ # [packages] STORAGE_PATH
attachments/ # [attachment] PATH
sessions/ # [session] PROVIDER_CONFIG (file mode)
indexers/ # bleve search indexes
queues/ # async task queues
tmp/ # temporary archives, uploads
log/ # [log] ROOT_PATH (default: WORK_PATH/log)
Native install from binary (this RPM: gitea-service)
FHS-compliant layout using systemd, sysusers, tmpfiles.
/usr/libexec/gitea # binary
/etc/gitea/app.ini # config (root:git 0640)
/var/lib/gitea/ # WORK_PATH (git:git 0750)
custom/
data/ # APP_DATA_PATH = data (relative)
git/
repositories/ # [repository] ROOT = data/git/repositories
lfs/ # [lfs] PATH = data/git/lfs
avatars/ # default: data/avatars
repo-avatars/ # default: data/repo-avatars
packages/ # default: data/packages
attachments/ # default: data/attachments
sessions/ # [session] PROVIDER = file
indexers/
queues/
actions_artifacts/
tmp/
log/ # [log] ROOT_PATH = log (relative)
/run/gitea/ # tmpfiles.d, runtime
Systemd unit: gitea.service
User=git,Group=git(UID/GID 124 via sysusers.d)WorkingDirectory=/var/lib/giteaEnvironment=GITEA_WORK_DIR=/var/lib/gitea- Database drop-in:
/usr/lib/systemd/system/gitea.service.d/database.conf(uncomment Wants/After for postgresql, redis, etc.)
SSH passthrough: /etc/ssh/sshd_config.d/50-gitea.conf
Match User git
AuthorizedKeysCommand /usr/libexec/gitea keys -c /etc/gitea/app.ini -e git -u %u -t %t -k %k
AuthorizedKeysCommandUser git
Docker / Podman rootful (gitea/gitea:1.25)
The standard Docker image runs as root and remaps paths under /data:
/data/
gitea/
conf/app.ini # config
avatars/ # AVATAR_UPLOAD_PATH
packages/ # STORAGE_PATH
... # APP_DATA_PATH = /data/gitea
git/
repositories/ # [repository] ROOT
lfs/ # [lfs] PATH
Volume: -v /host/path:/data
Docker / Podman rootless (gitea/gitea:1.25-rootless)
The rootless image flattens everything under /var/lib/gitea and /data:
/data/ # main volume
gitea/
conf/app.ini # config (mounted from host or volume)
db/ # sqlite (if used)
git/
repositories/ # [repository] ROOT
...
/var/lib/gitea/ # APP_DATA_PATH (secondary volume or subpath)
avatars/
repo-avatars/
packages/
attachments/
indexers/
queues/
actions_artifacts/
data/lfs/ # [lfs] PATH
tmp/
This RPM's quadlet (gitea.container) uses the rootless image and maps:
| Host path | Container path | Purpose |
|---|---|---|
/var/lib/gitea |
/data |
Repositories, config, git data |
/etc/gitea |
/data/gitea/conf |
app.ini (read-only intent) |
Elektronika Smodlaka (rootless container, custom app.ini)
Uses the rootless image via quadlet with APP_DATA_PATH = /var/lib/gitea and
explicit absolute paths. Git data is separated under git/ alongside data/.
/var/lib/gitea/ # WORK_PATH & APP_DATA_PATH
git/
repositories/ # [repository] ROOT
lfs/ # [lfs] PATH
data/
gitea.db # [database] PATH (sqlite fallback)
avatars/ # [picture] AVATAR_UPLOAD_PATH
repo-avatars/ # [picture] REPOSITORY_AVATAR_UPLOAD_PATH
attachments/ # [attachment] PATH
sessions/ # [session] PROVIDER_CONFIG (file)
log/ # [log] ROOT_PATH
custom/ # templates, public overrides
indexers/
queues/
actions_artifacts/
packages/
tmp/
/tmp/gitea/
local-repo/ # [repository.local] LOCAL_COPY_PATH
uploads/ # [repository.upload] TEMP_PATH
Key differences from defaults:
APP_DATA_PATH=/var/lib/gitea(notdata/relative) — everything under one rootgit/directory sits alongsidedata/(repos and LFS separated from app data)[log] ROOT_PATHgoes to/var/lib/gitea/data/log(inside data/, not alongside)- Uses postgres (
HOST = 127.0.0.1), but[database] PATHis set for sqlite fallback - Built-in SSH server on port 30009 (
START_SSH_SERVER = true)
Path comparison table
| Setting | Gitea Default | RPM app.ini | Docker Rootful | Docker Rootless | Elektronika Smodlaka |
|---|---|---|---|---|---|
WORK_PATH |
(binary dir) | /var/lib/gitea |
/data |
/data |
/var/lib/gitea |
APP_DATA_PATH |
data (relative) |
(default) data → .../gitea/data |
/data/gitea |
/var/lib/gitea |
/var/lib/gitea |
[repository] ROOT |
data/gitea-repositories |
data/git/repositories |
/data/git/repositories |
/data/git/repositories |
/var/lib/gitea/git/repositories |
[repository.local] LOCAL_COPY_PATH |
/tmp/gitea/local-repo |
(default) | (default) | (default) | /tmp/gitea/local-repo |
[repository.upload] TEMP_PATH |
/tmp/gitea/uploads |
(default) | (default) | (default) | /tmp/gitea/uploads |
[lfs] PATH |
data/lfs |
data/git/lfs |
/data/git/lfs |
/var/lib/gitea/data/lfs |
/var/lib/gitea/git/lfs |
[picture] AVATAR_UPLOAD_PATH |
data/avatars |
(default) data/avatars |
/data/gitea/avatars |
/var/lib/gitea/avatars |
/var/lib/gitea/data/avatars |
[picture] REPOSITORY_AVATAR_UPLOAD_PATH |
data/repo-avatars |
(default) data/repo-avatars |
/data/gitea/repo-avatars |
/var/lib/gitea/repo-avatars |
/var/lib/gitea/data/repo-avatars |
[attachment] PATH |
data/attachments |
(default) data/attachments |
(default) | (default) | /var/lib/gitea/data/attachments |
[session] PROVIDER_CONFIG |
data/sessions |
(default) data/sessions |
(default) | (default) | /var/lib/gitea/data/sessions |
[packages] STORAGE_PATH |
data/packages |
(default) data/packages |
/data/gitea/packages |
/var/lib/gitea/packages |
(default) |
[log] ROOT_PATH |
WORK_PATH/log |
log → /var/lib/gitea/log |
/data/gitea/log |
/var/lib/gitea/log |
/var/lib/gitea/data/log |
[database] PATH (sqlite) |
data/gitea.db |
(default) data/gitea.db |
(default) | (default) | /var/lib/gitea/data/gitea.db |
Directories that grow
| Directory | What it stores | Growth driver |
|---|---|---|
gitea-repositories/ |
Bare git repos | Number and size of repos |
lfs/ |
Git LFS objects | Large file usage |
packages/ |
Package registry artifacts | Package publishing |
indexers/ |
Bleve/Elasticsearch search indexes | Number of repos (if code indexer enabled) |
actions_artifacts/ |
CI/CD build artifacts | Gitea Actions usage |
queues/ |
Async task data | Usually small, grows during backlogs |
tmp/ |
Archive downloads, upload staging | Transient, reclaimed automatically |
To move heavy directories to separate storage, use absolute paths in app.ini:
[repository]
ROOT = /mnt/large_disk/repositories
[packages]
STORAGE_PATH = /mnt/large_disk/packages
Quadlet setup (gitea-container)
The container subpackage installs Podman quadlet files to
/usr/share/containers/systemd/:
| File | Purpose |
|---|---|
gitea.pod |
Pod definition, publishes port 1240:3000 |
gitea.container |
Gitea rootless container |
gitea-db.container |
PostgreSQL 18 container |
Enabled via drop-in: /etc/containers/systemd/gitea.pod.d/enable.conf
(WantedBy=multi-user.target)
Database credentials are passed as environment variables in the container
definition. For production, replace these with Podman secrets or a
POSTGRES_PASSWORD_FILE approach.
SSH passthrough for containers uses a shim script:
/usr/libexec/gitea-keys- callspodman execinto the container/usr/libexec/gitea-container- shim sogiteaCLI commands work from host
# /etc/ssh/sshd_config.d/50-gitea.conf (container variant)
Match User git
AuthorizedKeysCommand /usr/libexec/gitea-keys %u %t %k
AuthorizedKeysCommandUser root
Note: AuthorizedKeysCommandUser is root (not git) because podman exec
requires elevated privileges when run from sshd.
Quick start
Native binary
sudo dnf install gitea gitea-service
# Edit /etc/gitea/app.ini (database, domain, etc.)
# Uncomment database dependency in /usr/lib/systemd/system/gitea.service.d/database.conf
sudo systemctl daemon-reload
sudo systemctl enable --now gitea
Container (Podman quadlet)
sudo dnf install gitea gitea-container
# Edit /etc/gitea/app.ini
# The pod auto-starts via the enable.conf drop-in
sudo systemctl daemon-reload
sudo systemctl start gitea-pod
Gitea will be available at http://localhost:1240 (container) or
http://localhost:3000 (native).
Migration: Elektronika Smodlaka → RPM native layout
Copies data from the Elektronika Smodlaka container layout
(/srv/storage/gitea-data/data) to the RPM's native install structure
(/var/lib/gitea). The RPM app.ini groups repos and LFS under data/git/.
| Source (Elektronika) | Destination (RPM) | Notes |
|---|---|---|
git/repositories/ |
data/git/repositories/ |
Same name, different parent |
git/lfs/ |
data/git/lfs/ |
Same name, different parent |
data/avatars/ |
data/avatars/ |
Same relative path |
data/repo-avatars/ |
data/repo-avatars/ |
Same relative path |
data/attachments/ |
data/attachments/ |
Same relative path |
data/sessions/ |
data/sessions/ |
Same relative path |
data/log/ |
log/ |
RPM uses WORK_PATH/log |
indexers/ |
data/indexers/ |
Moves under APP_DATA_PATH |
queues/ |
data/queues/ |
Moves under APP_DATA_PATH |
actions_artifacts/ |
data/actions_artifacts/ |
Moves under APP_DATA_PATH |
packages/ |
data/packages/ |
Moves under APP_DATA_PATH |
custom/ |
custom/ |
Same relative path |
#!/bin/bash
set -euo pipefail
SRC="/srv/storage/gitea-data/data"
DST="/var/lib/gitea"
# Stop gitea before migration
# systemctl stop gitea
# Git data (repositories + LFS) → data/git/
rsync -av --progress "$SRC/git/repositories/" "$DST/data/git/repositories/"
rsync -av --progress "$SRC/git/lfs/" "$DST/data/git/lfs/"
# App data dirs that keep the same relative path under data/
for dir in avatars repo-avatars attachments sessions; do
if [ -d "$SRC/data/$dir" ]; then
rsync -av --progress "$SRC/data/$dir/" "$DST/data/$dir/"
fi
done
# Logs (data/log → log — RPM default is WORK_PATH/log)
if [ -d "$SRC/data/log" ]; then
rsync -av --progress "$SRC/data/log/" "$DST/log/"
fi
# Dirs that were at root in Elektronika, move under data/ for RPM
for dir in indexers queues actions_artifacts packages; do
if [ -d "$SRC/$dir" ]; then
rsync -av --progress "$SRC/$dir/" "$DST/data/$dir/"
fi
done
# Custom templates/public files
if [ -d "$SRC/custom" ]; then
rsync -av --progress "$SRC/custom/" "$DST/custom/"
fi
# SQLite database (only if present, RPM uses postgres by default)
if [ -f "$SRC/data/gitea.db" ]; then
rsync -av --progress "$SRC/data/gitea.db" "$DST/data/gitea.db"
fi
# Fix ownership
chown -R git:git "$DST"
echo "Migration complete. Verify /etc/gitea/app.ini paths match RPM app.ini."
The RPM app.ini only sets two paths explicitly (rest are defaults):
[repository]
ROOT = data/git/repositories ;; was: /var/lib/gitea/git/repositories (absolute)
[lfs]
PATH = data/git/lfs ;; was: /var/lib/gitea/git/lfs (absolute)
;; [log] ROOT_PATH = log ;; RPM default, resolves to /var/lib/gitea/log
;; ;; Elektronika had: /var/lib/gitea/data/log