Developer Tools guide

Gitea Docker Compose Setup: Repositories, SSH, and Backups Done Right

Deploy Gitea with durable repository storage, correct UID/GID ownership, HTTP and SSH URLs, a reverse proxy, and tested backups.

Published and reviewed by OpenAlt · September 22, 2026

Top-down view of an office Kanban board with colorful sticky notes for task management and organization.
Photo by cottonbro studio on Pexels

Table of Contents

Decide rootful or rootless

Choose the Gitea image family before deployment and keep it consistent. Gitea’s official Docker installation guide warns that rootful and rootless images are not interchangeable. Switching between them without planning can cause ownership and filesystem problems.

For a small internal team, rootful Compose is generally the simpler starting point because the container, bind mount, and host ownership model are easier to inspect. Rootless may better match a host security policy, but it requires the matching image and consistent permissions.

DecisionOperational consequence
Rootful imageConventional choice for a standard Docker host
Rootless imageRequires the matching rootless image and permissions
Bind mountKeeps repositories and configuration outside the container
Named volumeEasier Docker management, but less direct inspection

Do not combine a rootful image with a rootless data directory or change image families during an update without a documented migration and verified backup.

Preflight

Before starting the service, choose the image family, reserve the hostname, confirm the ports, and decide where /data will live.

  • Confirm Docker Compose is available.
  • Choose a stable directory for the Gitea bind mount.
  • Select host values for the Gitea UID and GID.
  • Decide whether users connect directly or through a reverse proxy.
  • Reserve HTTP port 3000 and SSH host port 222.
  • Choose SQLite or an externally managed database.
  • Record the public web URL and SSH clone address.
  • Decide where backups will be stored and how restoration will be tested.

The official guide documents the image, /data volume, and default port mappings. Use it as the baseline for your Compose file: read the Docker installation instructions.

Compose service and persistent data

Mount a durable host directory to /data and publish HTTP and SSH with the required mappings.

services:
  server:
    image: docker.gitea.com/gitea
    container_name: gitea
    environment:
      - USER_UID=${GITEA_UID}
      - USER_GID=${GITEA_GID}
    restart: always
    volumes:
      - ./gitea:/data
    ports:
      - "3000:3000"
      - "222:22"

Save this as compose.yaml, create the gitea directory beside it, and set GITEA_UID and GITEA_GID to values that match the intended ownership model. The container should be replaceable without removing repositories or configuration.

UID/GID and /data

Make the host-mounted directory writable by the identity expected by the selected image. If Gitea starts but cannot create repositories, attachments, or configuration files, inspect ownership and permissions before changing application settings.

id
ls -ld ./gitea
docker compose config

Repository storage and configuration must remain on the persistent mount. A running container does not prove that the mount is writable or that updates will preserve data. The Docker documentation explains the storage layout and image assumptions.

HTTP and SSH URLs

The Compose mapping publishes HTTP on host port 3000 and maps host port 222 to the container’s SSH port 22. When users connect directly, SSH clone URLs must include port 222.

ssh://git@your-host.example:222/team/project.git

The hostname is deployment-specific, but the displayed clone URL must match the address users can reach. If SSH is exposed through another external port, configure Gitea’s public SSH values so generated links remain usable.

Reverse proxy and root URL

Set Gitea’s public root URL to the address users actually enter, particularly when a reverse proxy terminates HTTPS. Gitea may listen internally on port 3000 while users access an HTTPS hostname; both layers must describe the same public service.

A typical arrangement is:

  1. The reverse proxy receives HTTPS traffic for the chosen hostname.
  2. It forwards requests to Gitea on port 3000.
  3. Gitea’s ROOT_URL uses the public HTTPS address.
  4. SSH remains reachable through the configured host port.
  5. Generated web links and clone URLs are checked after login.

If Gitea generates http://localhost, an internal container name, or an SSH URL with the wrong port, correct the root URL and SSH settings before inviting users. Consult the reverse proxy documentation and configuration cheat sheet.

Detailed view of a server rack with a focus on technology and data storage.
Photo by panumas nikhomkhai on Pexels
Detailed image of illuminated server racks showcasing modern technology infrastructure.
Photo by panumas nikhomkhai on Pexels

First admin and verification

Create the first administrator through the initial web setup, then test a complete repository workflow. Container health alone does not prove that users can clone or push.

  1. Validate the Compose file.

    docker compose config
    
  2. Start Gitea.

    docker compose up -d
    
  3. Check status and logs.

    docker compose ps
    docker compose logs --tail=100 server
    
  4. Open the configured web URL, complete setup, and create the administrator account.

  5. Test HTTP.

    curl -I http://your-host.example:3000
    
  6. Test SSH reachability.

    ssh -T -p 222 git@your-host.example
    
  7. Create a small test repository, clone it using the generated SSH URL, commit a change, and push it.

If the generated URL differs from the address you tested, stop and correct the root URL or SSH configuration. The reverse proxy guidance and configuration reference cover the relevant public-address settings.

Backup, update, and rollback

Back up Gitea before updates and preserve everything needed to reconstruct the deployment. Follow the official backup and restore documentation, especially when using an external database.

Include these items:

  • The persistent /data directory, including repositories and configuration.
  • The database, whether local or separately managed.
  • The Compose file and required environment values.
  • Reverse-proxy configuration for the public URL.
  • The image reference used by the current deployment.

After completing a backup, update with the intended image reference:

docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 server

Repeat the HTTP and clone/push tests after the update. For rollback, restore the previous image reference and use the documented restore procedure if database or application changes require it. Do not delete or overwrite /data while troubleshooting; retain the original directory until the replacement has been verified.

Common failures

SymptomLikely causeCorrective check
Container runs but cannot create dataUID/GID or directory permissionsInspect id, ownership, and logs
Web UI works but SSH clone failsPort 222 is blocked, unmapped, or misadvertisedCheck the mapping and generated URL
Clone URL uses the wrong hostnameIncorrect ROOT_URL or SSH settingsCompare the URL with the reachable address
Reverse proxy returns an errorIncorrect proxy target or forwarding configurationCheck the destination on port 3000
Data disappears after recreation/data was not mounted persistentlyInspect the Compose volume definition

When the cause is unclear, use the official Docker, reverse-proxy, and configuration references rather than assuming a restart repaired storage or URL settings.

Choose the right OpenAlt reference

Inspect Gitea on OpenAlt for the matching project page before deployment. For related self-hosted tools, browse the project-management directory, and use OpenAlt’s difficulty methodology to understand the operational work involved.

Frequently Asked Questions

SQLite or Postgres?

SQLite is simpler for a small internal installation when keeping the database close to Gitea is acceptable. Postgres fits better when your team already operates it separately or wants database administration outside the Gitea container. Include either database in the backup and restore procedure described in Gitea’s official documentation.

Why port 222?

Port 222 avoids competing with the host’s usual SSH service while mapping to Gitea’s container port 22. The mapping is 222:22, and clone URLs must include the externally reachable SSH port.

Can rootful switch to rootless?

Not as a casual image replacement. Rootful and rootless images are not interchangeable. A switch requires planned migration, compatible ownership, and a verified backup. Keep the original /data directory until the new deployment can clone and push successfully.

What belongs in a backup?

Back up persistent /data, the database, Compose and environment configuration, and reverse-proxy settings required to restore the public URL. Follow the backup and restore guide, then test the restored service by accessing repositories and completing a clone/push workflow.

Do I need a reverse proxy?

No. Users can access Gitea directly through host port 3000, provided that the hostname and generated URLs are correct. A reverse proxy is useful when HTTPS or a public hostname is handled outside the container. In that case, set Gitea’s root URL to the public address and forward requests to port 3000.