How to Install Forgejo on Docker: A Lightweight Self-Hosted Git Solution

How to Install Forgejo on Docker: A Lightweight Self-Hosted Git Solution

Bitnesia Development Sep 1, 2026 2 ID

Forgejo is a self-hosted platform for managing Git repositories, similar to GitHub or GitLab, but running fully on our own server. This article covers installing Forgejo via Docker from scratch: setting up Docker Engine, running containers, enabling HTTPS, up to creating your first repository. All commands here were tested against Ubuntu Server 26.04 LTS and Forgejo version 16.0.3.

What Is Forgejo?

Forgejo is an open-source Git server software created as a soft-fork of Gitea in late 2022. Unlike Gitea, Forgejo is managed by an independent community under Codeberg, without the involvement of a single commercial entity controlling development direction.

We can use Forgejo to replace GitHub or GitLab when teams want full control over source code data, without limits on private repositories or reliance on third-party services. Compared to GitLab Community Edition which is heavy for small servers, Forgejo is much lighter because it is written in Go and only needs a single binary to run.

Sysadmins managing internal infrastructure usually choose Forgejo to host code belonging to Developer teams, while ensuring that source code never leaves the company network.

Pre-Installation Requirements

Official Forgejo documentation does not list exact figures for minimum CPU and RAM requirements. As a practical overview, a small instance with a SQLite database and a few users can run comfortably on 1 vCPU and 1 GB RAM. For teams with higher traffic and PostgreSQL database, allocate a minimum of 2 vCPU and 2-4 GB RAM. Note carefully: this is a practical estimation, not an official specification from the Forgejo team, so keep monitoring resource usage after the instance is running.

What must be prepared before starting:

  • A server with Ubuntu Server 26.04 LTS (or another Linux distribution supporting Docker)
  • Docker Engine and Docker Compose plugin
  • A domain or subdomain already pointed to the server IP, if enabling HTTPS
  • Ports 80, 443, and a custom SSH port (we use 222 in this article) not used by other services
  • Minimum disk space of 10 GB, growing according to the number and size of repositories

For databases, Forgejo supports SQLite (no extra installation needed), PostgreSQL version 14 and above, MySQL version 8.4 and above, and MariaDB version 10.6 and above. SQLite is suitable for small instances or evaluation, while PostgreSQL is a more mature choice for production because it is robust in handling concurrent queries from many users.

Installing Docker Engine on Ubuntu

Follow these four sequential steps to install Docker Engine from the official repository.

  1. Remove old Docker packages that might be installed from non-official repositories. Packages like docker.io, docker-compose, or podman-docker often conflict with official Docker Engine.

    sudo apt remove docker.io docker-compose podman-docker
  2. Add GPG key and official Docker repository to the system.

    sudo apt update
    sudo apt install ca-certificates curl
    sudo install -m 0755 -d /etc/apt/keyrings
    sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
    sudo chmod a+r /etc/apt/keyrings/docker.asc
    
    sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
    Types: deb
    URIs: https://download.docker.com/linux/ubuntu
    Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
    Components: stable
    Architectures: $(dpkg --print-architecture)
    Signed-By: /etc/apt/keyrings/docker.asc
    EOF
    
    sudo apt update
  3. Install Docker Engine along with Buildx and Compose plugins.

    sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  4. Verify installation by running the official Docker test image. If the message "Hello from Docker!" appears, installation is successful and ready to run Forgejo.

    sudo docker run hello-world

Preparing Folder Structure and Compose File

Create a working directory to store configuration files and Forgejo data. Separate the data folder so it is easy to backup independently from the compose file.

mkdir -p ~/forgejo/{forgejo,postgres}
cd ~/forgejo

Next, create a compose.yaml file in that directory. This file defines the Forgejo service, database, network, and volumes shared together.

Two important environment variables here are USER_UID and USER_GID. Both determine file ownership inside the data volume. The volume folder on the host must be owned by the same UID/GID values; otherwise, the container may fail to start due to permission issues.

Choosing a Database: SQLite or PostgreSQL

SQLite is the simplest choice because it does not require additional services, suitable for small instances or initial evaluations. Its drawback is that SQLite is less optimal when many processes write to the database simultaneously, something common in instances with many active Developers.

For production needs, we use PostgreSQL. Below is the complete compose.yaml content with PostgreSQL as the database backend:

services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:16.0.3
    container_name: forgejo
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - FORGEJO__database__DB_TYPE=postgres
      - FORGEJO__database__HOST=db:5432
      - FORGEJO__database__NAME=forgejo
      - FORGEJO__database__USER=forgejo
      - FORGEJO__database__PASSWD=replace-with-strong-password
    restart: always
    networks:
      - forgejo
    volumes:
      - ./forgejo:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "222:22"
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    container_name: forgejo-db
    restart: always
    environment:
      - POSTGRES_USER=forgejo
      - POSTGRES_PASSWORD=replace-with-strong-password
      - POSTGRES_DB=forgejo
    networks:
      - forgejo
    volumes:
      - ./postgres:/var/lib/postgresql/data

networks:
  forgejo:
    external: false

Important: replace replace-with-strong-password in both places with the same strong password. Do not leave default credentials saved on a production server.

The naming format for environment variables like FORGEJO__database__DB_TYPE follows the FORGEJO__[SECTION]__[KEY] pattern, mapping directly to Forgejo app.ini configuration parameters. This pattern applies to almost all Forgejo configuration options, allowing us to configure many aspects via environment variables without entering the container.

Running the Forgejo Container

Follow these three sequential steps to run Forgejo and ensure the container is stable.

  1. Align folder ownership with the UID/GID defined in the compose file.

    sudo chown -R 1000:1000 ~/forgejo/forgejo
  2. Run the entire stack using Docker Compose.

    sudo docker compose up -d
  3. Check container status to ensure both run without a restart loop.

    sudo docker compose ps

If Forgejo status continuously changes from Up to Restarting, check logs to find root causes, usually related to database connections or folder permissions:

sudo docker compose logs -f forgejo

Initial Configuration via Web Installer

Once the container is stable, access http://server-address:3000 in your browser. Because database credentials were set via environment variables, the installation wizard usually displays basic configuration pages directly, such as application name, server URL, and initial admin user creation.

Fill out the Administrator Account Settings section with admin username, email, and password. This account has full access to the entire instance, so use a strong and unique password.

Alternatively, we can create an admin account via the command line interface without exposing the web installer publicly, which is safer for production servers:

sudo docker exec forgejo forgejo admin user create \
  --username admin \
  --password replace-admin-password \
  --email [email protected] \
  --admin

This CLI approach is useful if we want to block port 3000 access externally from the start and only open access through a reverse proxy after the first admin is created.

Reverse Proxy with Nginx

Running Forgejo directly on port 3000 without a reverse proxy is not recommended for production. Using Nginx as a reverse proxy makes it easy to configure HTTPS, domains, and security headers in one place.

  1. Install Nginx via package manager.

    sudo apt install nginx
  2. Create a new configuration file at /etc/nginx/conf.d/forgejo.conf.

    server {
        listen 80;
        listen [::]:80;
        server_name git.example.com;
        merge_slashes off;
    
        location / {
            proxy_pass http://127.0.0.1:3000;
            proxy_set_header Connection $http_connection;
            proxy_set_header Upgrade $http_upgrade;
            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;
            client_max_body_size 512M;
        }
    }

    The X-Real-IP and X-Forwarded-For headers are important so Forgejo logs original IP addresses from Visitors or Developers rather than Nginx internal IP. Setting merge_slashes off prevents Nginx from merging consecutive slashes in URLs, which could interfere with certain Git endpoints. Setting client_max_body_size 512M increases upload limits, essential for pushing large repositories.

  3. Test configuration and reload Nginx.

    sudo nginx -t
    sudo systemctl reload nginx

Enabling HTTPS with Let's Encrypt

Once the domain points to the server and Nginx runs normally on port 80, it is time to enable HTTPS. Ensure ports 80 and 443 are allowed in the firewall before proceeding.

  1. Install Certbot along with its Nginx plugin.

    sudo apt install certbot python3-certbot-nginx
  2. Run Certbot for the target domain. Certbot automatically injects SSL configurations into the existing Nginx file and redirects HTTP traffic to HTTPS.

    sudo certbot --nginx -d git.example.com

Let's Encrypt certificates are valid for 90 days, but the certbot package in Ubuntu includes a systemd timer handling automatic renewals. Check its schedule with:

sudo systemctl list-timers | grep certbot

Configuring DOMAIN, SSH_DOMAIN, and ROOT_URL in app.ini

After HTTPS is active and the domain resolves to the server via Nginx, it is time to adjust core Forgejo options determining how URLs are constructed for web links, webhook payloads, and repository clone URLs displayed on repository pages.

The main Forgejo configuration file is located at ~/forgejo/forgejo/gitea/conf/app.ini on the host, mapped from the ./forgejo:/data volume created initially (path inside container: /data/gitea/conf/app.ini). Open this file with a text editor, find the [server] section, and adjust these three options:

[server]
DOMAIN         = git.example.com
ROOT_URL       = https://git.example.com/
SSH_DOMAIN     = git.example.com
SSH_PORT       = 222
  • DOMAIN is used by Forgejo to construct internal URLs and domain cookies. Its value must match the public domain accessed via Nginx; if different, features like login redirects or webhooks may target wrong addresses.
  • ROOT_URL is the complete URL appearing in web links, webhook payloads, and HTTPS clone URLs on repository pages. It must use the https:// scheme because SSL terminates at Nginx rather than Forgejo itself.
  • SSH_DOMAIN and SSH_PORT define the domain and port displayed under the SSH tab on the Clone button. Align SSH_PORT with the mapped port 222 in compose.yaml so displayed SSH clone URLs are correct without requiring manual edits by Developers.

Save changes and restart the container so new configurations take effect:

sudo docker compose restart forgejo

SSH Setup for Git Operations

Forgejo SSH port was mapped to 222 on the host via compose.yaml earlier, avoiding conflicts with the main server SSH daemon running on port 22. Because the port is non-standard and each Developer has dedicated keys for Forgejo, the most practical approach is storing connection details in individual user SSH client configs so users do not need to retype port and key paths for every push or pull. Developers follow these five sequential steps.

  1. Generate a dedicated SSH key pair for Forgejo on client machines. Using a separate filename (instead of default id_ed25519) makes management easier when users have other keys for different purposes.

    ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/id_ed25519_forgejo
  2. Add the following entry to the user's ~/.ssh/config file (create the file if it does not exist).

    Host git.example.com
        HostName git.example.com
        Port 222
        User git
        IdentityFile ~/.ssh/id_ed25519_forgejo
        IdentitiesOnly yes

    With this configuration, the SSH client automatically uses port 222 and the correct key whenever the host matches git.example.com, without needing -p 222 or -i flags added manually in commands.

  3. Copy public key contents (~/.ssh/id_ed25519_forgejo.pub) and paste them into Settings > SSH / GPG Keys under respective Forgejo user accounts.

  4. Test connection from the client side. Thanks to ~/.ssh/config, the correct port and key are automatically used. If a welcome message from Forgejo showing the username appears, SSH connectivity works properly.

    ssh -T [email protected]
  5. Clone repository via SSH. Since the port is defined in ~/.ssh/config, the URL format remains as simple as standard SSH cloning without embedding port numbers.

    git clone [email protected]:username/repo-name.git

Creating Your First Repository

Follow these three sequential steps to test end-to-end Git workflows on the newly installed instance.

  1. Log in to the Forgejo dashboard at https://git.example.com, then click New Repository. Fill in repository name, brief description, and choose visibility (public or private) based on team needs.

  2. Clone the newly created repository locally via HTTPS or SSH.

    git clone https://git.example.com/username/repo-name.git
    cd repo-name
  3. Push an initial commit to ensure authentication flow through server storage works normally.

    echo "# Repo Name" > README.md
    git add README.md
    git commit -m "Initial commit"
    git push origin main

If the push succeeds without errors, the entire chain from containers, database, reverse proxy, HTTPS, to SSH is confirmed working.

Backup and Updating Forgejo

Forgejo provides a built-in forgejo dump command that compresses databases, repositories, logs, and other supporting data into a single archive file. Run it inside the container:

sudo docker exec forgejo forgejo dump -f /data/forgejo-backup.zip

Since /data inside the container links to ~/forgejo/forgejo on the host via volume mapping, backup files automatically appear in that directory and can be copied to separate storage. Schedule this command via cron for regular backups, for example every night.

Updating to the latest Forgejo version follows three sequential steps.

  1. Open compose.yaml and change the forgejo image tag to the target version.
  2. Pull the new image.

    sudo docker compose pull forgejo
  3. Restart the container using the new image.

    sudo docker compose up -d forgejo

Check official changelogs before upgrading to major releases. Some major releases bring database schema changes requiring automatic migrations on container startup, so ensure fresh backups are available before running updates.

Essential Basic Security Measures

The following steps are recommended baseline standards before using the instance in production:

  • Restrict firewall access to ports 80, 443, and custom SSH port (222). Do not expose port 3000 directly to the internet, as traffic should route through Nginx on localhost.
  • Disable public registration using FORGEJO__service__DISABLE_REGISTRATION=true in the compose file, unless public registration is explicitly intended.
  • Enable two-factor authentication for admin accounts, so attackers obtaining passwords still cannot log in without a second factor.
  • Monitor SSH and HTTP access logs regularly to detect brute-force attempts early.

Troubleshooting Common Issues

Common issues encountered during setup:

Container keeps restarting after docker compose up. Most commonly caused by volume folders lacking proper UID/GID ownership. Re-run sudo chown -R 1000:1000 ~/forgejo/forgejo, then restart the container.

Nginx shows 502 Bad Gateway error. Usually means the Forgejo container is not running or proxy_pass points to an incorrect port. Verify using docker compose ps and ensure port 3000 in proxy_pass matches mapped ports in the compose file.

SSH connection refused. Check whether port 222 is open on the server firewall and not conflicting with other services. Run sudo ss -tulpn | grep 222 to verify the container is actively listening on the port.

Push or pull rejected with permission denied message. Ensure your public key is attached to the correct account under SSH Keys menu, and that the user has write permissions to the destination repository.

Conclusion

Forgejo on Docker offers a lightweight, fast-to-deploy self-hosted Git server that is easy to maintain using a single compose.yaml file. Resource-wise, this instance is much lighter than GitLab without losing essential features required daily by Developer teams: pull requests, issue trackers, and Actions for CI/CD automation.

Did this solve your problem? Consider leaving a tip to show your appreciation!

Say Thanks with a Tip

Related Posts