Skip to content
Author Nejat Hakan
eMail nejat.hakan@outlook.de
PayPal Me https://paypal.me/nejathakan


Git Server Forgejo

Introduction

Welcome to the comprehensive guide on self-hosting your own Git server using Forgejo. In the modern software development landscape, version control systems (VCS) are indispensable tools. Git has emerged as the de facto standard for VCS, enabling developers to track changes, collaborate effectively, and manage codebases of all sizes. While popular hosted services like GitHub, GitLab, and Bitbucket offer convenience, self-hosting your Git server provides unparalleled control, privacy, security, and customization.

Forgejo is a community-driven, lightweight, and easy-to-set-up self-hosted Git service. It originated as a fork of Gitea, aiming for a more community-led governance model while maintaining compatibility and focusing on stability and usability. Think of Forgejo as your personal powerhouse for managing Git repositories, similar in core functionality to GitHub or GitLab, but running entirely on your own infrastructure.

Why Choose Forgejo for Self-Hosting?

  • Lightweight: Forgejo is written in Go, resulting in a single binary with minimal resource consumption compared to heavier platforms like GitLab. This makes it ideal for running on modest hardware, including Raspberry Pis or small virtual private servers (VPS).
  • Full Control: You own your data. No third-party terms of service changes, unexpected feature deprecations, or pricing model shifts will affect your core operations. You decide the update schedule, the features you enable, and the security policies you enforce.
  • Enhanced Privacy: Your code repositories, issues, and project discussions remain on your server, reducing exposure to external data breaches or surveillance.
  • Customization: Tailor Forgejo's appearance, integrate it with your existing authentication systems (LDAP, OAuth), and extend its functionality through webhooks or its API.
  • Cost-Effective: While hosted platforms often have free tiers, advanced features or larger teams typically require paid subscriptions. Self-hosting Forgejo eliminates these recurring costs, with expenses limited to your server hardware/hosting and maintenance time.
  • Community Driven: Forgejo is developed and maintained by an active community, ensuring its continued evolution and responsiveness to user needs, independent of single corporate control.
  • Learning Opportunity: Setting up and managing your own Git server is an excellent way to deepen your understanding of Git, web servers, databases, networking, and system administration.

Prerequisites

Before embarking on this journey, we assume you have a foundational understanding of the following concepts and access to the necessary resources:

  • Linux Command Line: Familiarity with navigating the shell, editing files (using nano, vim, or similar), managing services, and understanding file permissions. Most examples will use Debian/Ubuntu-based commands (apt).
  • Basic Networking: Understanding IP addresses, ports, and DNS.
  • Git Fundamentals: Knowledge of basic Git commands like clone, add, commit, push, pull, branch, and merge.
  • Server Environment: Access to a server (physical machine, virtual machine, or VPS) running a Linux distribution. At least 1 CPU core, 512MB RAM (1GB+ recommended for smoother operation, especially with CI/CD), and sufficient disk space for your repositories and the Forgejo installation.
  • (Optional but Recommended) Docker and Docker Compose: While Forgejo can be installed from a binary, using Docker simplifies installation, upgrades, and dependency management significantly. We will heavily feature the Docker approach.
  • (Optional) Domain Name: A registered domain name is required if you want to access your Forgejo instance via a memorable name and secure it with HTTPS/SSL certificates.

This guide is structured progressively, starting with basic installation and usage, moving to intermediate configurations like HTTPS and database management, and finally covering advanced topics such as CI/CD, customization, and security hardening. Each section includes theoretical explanations followed by hands-on workshops to solidify your learning. Let's begin!

1. Basic Setup and Usage

This section covers the essentials to get your Forgejo instance up and running, configure it for the first time, and perform fundamental Git operations. We'll primarily use Docker for ease of deployment.

Understanding Forgejo and Self-Hosting Benefits Revisited

Before we dive into the installation, let's reiterate the core concepts. Forgejo acts as a central hub for your Git repositories. It provides a web interface for browsing code, managing issues, tracking pull requests (or merge requests), managing users and organizations, and much more. Unlike simply having Git installed locally, Forgejo provides the server component – the remote location (origin in Git terms) where collaborators can push and pull changes.

The benefits of self-hosting, as mentioned earlier, revolve around control, privacy, and customization. Imagine your university project group needing a private space to collaborate on code without relying on external services or facing limitations of free tiers. Or consider a personal project where you want absolute ownership over your codebase and its history. Forgejo empowers these scenarios. It's not just about storing code; it's about creating a dedicated, controlled environment for software development collaboration.

Compared to alternatives:

  • vs. GitHub/GitLab.com: Forgejo offers privacy and control, no vendor lock-in, but requires you to manage the infrastructure (updates, backups, security).
  • vs. Self-Hosted GitLab: Forgejo is significantly more lightweight, requiring fewer server resources. GitLab offers a broader feature set out-of-the-box (advanced CI/CD, security scanning, etc.), often appealing to larger enterprises, but with higher complexity and resource demands.
  • vs. Gitea: Forgejo is a fork of Gitea, focused on a community governance model (Codeberg drives much of its development). Functionality is very similar, and migration between them is generally straightforward. Forgejo aims for stability and a transparent development process.

Choosing Forgejo means opting for a balance of features, performance, and community-driven development, ideal for individuals, small teams, educational settings, and organizations prioritizing resource efficiency and control.

Workshop Setting Up Prerequisites

This workshop ensures your server environment is ready for the Forgejo installation using Docker.

Objective: Install Docker and Docker Compose on your Linux server.

Assumptions: You are logged into your Linux server (Ubuntu/Debian assumed) via SSH or direct console access with sudo privileges.

Steps:

  1. Update Package Lists: Ensure your package manager has the latest list of available software.

    sudo apt update
    

    • Explanation: apt update downloads package information from all configured sources. It's crucial to do this before installing new packages to ensure you get the latest versions and dependencies are correctly resolved. sudo executes the command with administrative privileges.
  2. Install Prerequisite Packages: Docker installation requires a few helper packages.

    sudo apt install -y apt-transport-https ca-certificates curl software-properties-common gnupg lsb-release
    

    • Explanation:
      • apt-transport-https: Allows apt to retrieve packages over HTTPS.
      • ca-certificates: Allows the system to check security certificates.
      • curl: A tool to transfer data from or to a server (used here to download Docker's GPG key).
      • software-properties-common: Provides scripts for managing software sources.
      • gnupg: GNU Privacy Guard, used for handling cryptographic keys (like Docker's repository key).
      • lsb-release: Provides information about the Linux Standard Base and distribution.
      • -y: Automatically answers "yes" to prompts during installation.
  3. Add Docker's Official GPG Key: This verifies the authenticity of the Docker packages.

    sudo install -m 0755 -d /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
    sudo chmod a+r /etc/apt/keyrings/docker.gpg
    

    • Explanation:
      • The first command creates a directory (/etc/apt/keyrings) to store GPG keys, ensuring proper permissions (0755).
      • curl -fsSL ... downloads the GPG key. -f fails silently on server errors, -s runs in silent mode, -S shows errors if -s is used, -L follows redirects.
      • | sudo gpg --dearmor -o ... pipes the downloaded key to the gpg command. --dearmor converts the key from ASCII-armored format to the binary format apt uses. -o specifies the output file.
      • The final chmod ensures the key file is readable by all users, which apt requires.
  4. Add the Docker Repository: Configure apt to know where to download Docker from.

    echo \
      "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
      $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
    

    • Explanation:
      • echo "..." constructs the repository source line.
      • arch=$(dpkg --print-architecture) dynamically inserts your server's architecture (e.g., amd64, arm64).
      • signed-by=... tells apt which key to use to verify packages from this repository.
      • $(lsb_release -cs) dynamically inserts your Ubuntu/Debian version codename (e.g., jammy, bullseye).
      • stable indicates you want the stable release channel of Docker.
      • | sudo tee ... writes the output to the specified file (/etc/apt/sources.list.d/docker.list). tee is used so it can write the file with sudo privileges. > /dev/null suppresses the output of tee to the standard output.
  5. Update Package Lists Again: Now that the Docker repository is added, update apt's knowledge.

    sudo apt update
    

  6. Install Docker Engine, CLI, Containerd, and Docker Compose:

    sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
    

    • Explanation:
      • docker-ce: Docker Community Edition (the engine).
      • docker-ce-cli: Docker command-line interface.
      • containerd.io: A container runtime.
      • docker-buildx-plugin: Enables advanced build features with BuildKit.
      • docker-compose-plugin: Integrates docker compose command directly into the Docker CLI (newer method). Older systems might require installing docker-compose separately.
  7. Verify Docker Installation: Check that Docker is running.

    sudo docker run hello-world
    

    • Explanation: This command downloads a minimal test image and runs it in a container. If successful, it prints a confirmation message, indicating Docker is working correctly. You should see output like "Hello from Docker!".
  8. (Optional but Recommended) Add Your User to the docker Group: This allows you to run Docker commands without sudo.

    sudo usermod -aG docker $USER
    

    • Explanation: usermod -aG adds (-a) the user ($USER, which automatically expands to your current username) to the specified group (docker, -G).
    • Important: You need to log out and log back in (or run newgrp docker in your current shell) for this change to take effect. After logging back in, test with docker ps (without sudo).

Conclusion: You have successfully installed Docker Engine and Docker Compose on your server. Your environment is now prepared for deploying Forgejo using containers.

Installation with Docker

Docker simplifies the Forgejo installation immensely. We'll use docker-compose, a tool for defining and running multi-container Docker applications. This allows us to manage Forgejo and its potential database (if not using SQLite) together easily.

Core Concepts:

  • Image: A read-only template containing the application (Forgejo) and its dependencies. We'll use the official Forgejo image (codeberg.org/forgejo/forgejo).
  • Container: A runnable instance of an image. It's isolated from the host system and other containers.
  • Volume: A mechanism to persist data generated by containers outside the container's lifecycle. This is crucial for storing Forgejo's configuration, repositories, and database data. If the container is removed and recreated, data in volumes remains.
  • Port Mapping: Exposes ports from the container to the host machine, allowing you to access the Forgejo web interface and SSH service.
  • docker-compose.yml: A YAML file defining the services, networks, and volumes for the application.

Basic docker-compose.yml for Forgejo (using SQLite):

version: "3.8" # Specify compose file version

networks: # Define a network for services to communicate
  forgejo_net:
    external: false

services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:7.0 # Use the desired Forgejo version tag (check releases!)
    container_name: forgejo_server
    environment:
      - USER_UID=1000 # Run Forgejo process as UID 1000 inside the container
      - USER_GID=1000 # Run Forgejo process as GID 1000 inside the container
    networks: # Connect container to the defined network
      - forgejo_net
    volumes:
      - ./forgejo/data:/data # Map host directory to container's /data for persistent data
      - /etc/timezone:/etc/timezone:ro # Sync timezone with host (read-only)
      - /etc/localtime:/etc/localtime:ro # Sync localtime with host (read-only)
    ports:
      - "3000:3000" # Map host port 3000 to container port 3000 (Web UI)
      - "2222:22"   # Map host port 2222 to container port 22 (SSH) - Avoid host's SSH port 22!
    restart: unless-stopped # Automatically restart the container unless manually stopped
    depends_on: [] # Add database service name here if using external DB like PostgreSQL

# If you were using persistent Docker volumes instead of bind mounts:
# volumes:
#  forgejo_data:
#    driver: local

Explanation of the docker-compose.yml:

  • version: "3.8": Defines the version of the Compose file syntax.
  • networks: Declares a custom network forgejo_net. Containers on the same Docker network can easily communicate with each other by name. external: false means Docker Compose will create this network if it doesn't exist.
  • services: Defines the containers to be run.
    • forgejo: The name of our service.
      • image: Specifies the Docker image to use. codeberg.org/forgejo/forgejo:7.0 pulls version 7.0. It's good practice to pin to a specific version rather than latest for predictability. Always check the Forgejo releases page for current stable versions.
      • container_name: Assigns a specific name to the running container for easier identification.
      • environment: Sets environment variables inside the container. USER_UID and USER_GID are important for file permissions. They ensure that files created by Forgejo inside the volume have the correct ownership on the host machine. You should ideally match these to the user running Docker or a dedicated user on the host. 1000:1000 is common for the default user on many Linux systems.
      • networks: Attaches this service to the forgejo_net network.
      • volumes: Defines data persistence.
        • ./forgejo/data:/data: This is a bind mount. It maps the forgejo/data directory relative to where you run docker-compose up on your host machine to the /data directory inside the container. Forgejo stores all its vital data (repositories, configuration, attachments, etc.) within /data. This is the most critical part for data safety.
        • /etc/timezone:/etc/timezone:ro and /etc/localtime:/etc/localtime:ro: These map the host's time settings into the container (read-only) ensuring logs and timestamps within Forgejo match the host server time.
      • ports: Maps host ports to container ports (HOST:CONTAINER).
        • "3000:3000": Makes the Forgejo web UI (running on port 3000 inside the container) accessible on port 3000 of your host machine.
        • "2222:22": Makes the Forgejo SSH server (running on port 22 inside the container) accessible on port 2222 of your host machine. We use 2222 on the host to avoid conflicting with the standard SSH daemon typically running on port 22.
      • restart: unless-stopped: Policy for restarting the container. unless-stopped ensures the container restarts automatically if it crashes or if the Docker daemon restarts (e.g., after a server reboot), unless you explicitly stop it using docker stop forgejo_server or docker-compose stop.
      • depends_on: []: If you add a database service (like PostgreSQL) in the same docker-compose.yml, you would list its service name here (e.g., depends_on: [db]) to ensure Forgejo starts after the database is ready.

Workshop Deploying Forgejo using Docker Compose

Objective: Launch the Forgejo container using the docker-compose.yml file.

Assumptions: You have Docker and Docker Compose installed (from the previous workshop) and are in a terminal on your server.

Steps:

  1. Create a Project Directory: It's good practice to keep your Compose files and related data organized.

    mkdir forgejo-server
    cd forgejo-server
    

    • Explanation: Creates a directory named forgejo-server and changes the current directory into it. The ./forgejo/data volume path in the docker-compose.yml will now refer to forgejo-server/forgejo/data on your host.
  2. Create the docker-compose.yml File: Use a text editor like nano to create the file.

    nano docker-compose.yml
    

    • Explanation: Opens the nano text editor to create/edit the docker-compose.yml file.
  3. Paste the YAML Content: Copy the docker-compose.yml content provided in the section above and paste it into the nano editor.

    • Verify: Double-check the indentation (YAML is sensitive to spaces, typically 2 spaces per level) and ensure you've chosen a recent, stable Forgejo version tag for the image.
    • Save and Exit: In nano, press Ctrl+X, then Y to confirm saving, and Enter to confirm the filename.
  4. Create the Data Directory: The bind mount ./forgejo/data needs to exist on the host before starting the container, and Docker needs appropriate permissions.

    mkdir -p forgejo/data
    # Optional but recommended: Set permissions matching the UID/GID in the compose file
    # sudo chown 1000:1000 forgejo/data
    

    • Explanation: mkdir -p creates the directory and any necessary parent directories (forgejo in this case). The chown command (if you run it) changes the owner and group of the forgejo/data directory to UID 1000 and GID 1000, matching the USER_UID and USER_GID environment variables in the docker-compose.yml. This helps prevent potential permission issues when Forgejo tries to write data into the volume from within the container. You might need sudo for chown.
  5. Start Forgejo: Run Docker Compose in detached mode (-d).

    docker compose up -d
    # If you are on an older system without the compose plugin, use:
    # docker-compose up -d
    

    • Explanation: docker compose up reads the docker-compose.yml file, creates the network (if needed), pulls the Forgejo image (if not already present), creates, and starts the container(s) defined in the file. The -d flag runs the containers in the background (detached mode) so you get your terminal prompt back.
  6. Check Container Status: Verify that the container is running.

    docker ps
    # Or: docker compose ps
    

    • Explanation: docker ps lists currently running containers. You should see a container named forgejo_server listed, showing the ports it exposes and its status (e.g., Up X seconds).
  7. Check Logs (Optional but useful for troubleshooting):

    docker logs forgejo_server
    # Or: docker compose logs forgejo
    

    • Explanation: Displays the logs generated by the Forgejo application inside the container. This is the first place to look if the container doesn't start correctly or if you encounter issues later. Use docker logs -f forgejo_server to follow the logs in real-time.

Conclusion: Forgejo is now running inside a Docker container! It's listening on ports 3000 (HTTP) and 2222 (SSH) on your server's IP address. In the next step, we'll perform the initial web-based configuration.

Initial Configuration Wizard

When you first access the Forgejo web interface after a fresh installation, you'll be greeted by an installation page (/install). This wizard guides you through the essential configuration settings required for Forgejo to operate. These settings are then saved to the main configuration file, app.ini, located within the persistent data volume (/data/forgejo/conf/app.ini inside the container, which corresponds to ./forgejo/data/forgejo/conf/app.ini on your host).

Key Configuration Options:

  • Database Settings:

    • Database Type: Since we used the basic Docker setup without an external database, the default and recommended option here is SQLite3. It's simple, stores the database in a single file within the /data volume, and requires no separate database server. For more demanding scenarios (covered in Intermediate), you might choose PostgreSQL or MySQL/MariaDB.
    • Path: When using SQLite3, this specifies the location of the database file. The default (data/forgejo.db) within the container's /data directory is usually fine.
  • General Settings:

    • Site Title: The name displayed in the browser tab and on the site header (e.g., "My University Projects", "Personal Git Server").
    • Repository Root Path: The path inside the container where Git repository data will be stored. The default (/data/git/repositories) is correct for our Docker setup, as it falls within the mapped /data volume. Do not change this unless you have manually configured different volume mappings.
    • Run As Username: The system username Forgejo runs as inside the container. This should match the user configured by the Docker image (often git, which corresponds to the USER_UID/USER_GID we set). The default is usually correct.
    • SSH Server Domain: The domain name that users will use to clone/push repositories via SSH (e.g., git.yourdomain.com). If you don't have a domain yet, you can use your server's IP address for now.
    • SSH Server Port: The external port number mapped to the container's SSH service. In our docker-compose.yml, we mapped host port 2222 to container port 22. Therefore, you must set this to 2222. Forgejo needs to know this external port to display correct SSH clone URLs like ssh://git@yourserver.com:2222/user/repo.git.
    • Forgejo Base URL: This is critically important. It's the full URL users will use to access the web interface, including the protocol (http or https) and port (if not standard 80/443). For our initial setup, this will be http://<your_server_ip>:3000/. Ensure this is accurate, as Forgejo uses it to generate links and clone URLs.
    • HTTP Server Port: The port Forgejo listens on inside the container for web traffic. The default is 3000, which matches our docker-compose.yml.
  • Optional Settings (Can be configured later):

    • Email Service Settings: Configure if you want Forgejo to send notification emails (e.g., for registration, password reset, issue notifications). Requires SMTP server details.
    • Server and Third-Party Service Settings: Enable/disable features like federation, Gravatar, etc.
    • Administrator Account Settings: Crucial! You must create an initial administrator account here. Choose a strong username and password. This account will have full control over the Forgejo instance.

The app.ini File:

Once you complete the wizard, Forgejo creates the app.ini file. You can view or edit it later (requires restarting Forgejo for changes to take effect):

  • Inside container: /data/forgejo/conf/app.ini
  • On host: ./forgejo/data/forgejo/conf/app.ini (relative to your docker-compose.yml)

It's highly recommended to familiarize yourself with the structure and options available in app.ini by consulting the official Forgejo documentation.

Workshop Completing the Initial Setup

Objective: Access the Forgejo web interface and complete the initial configuration wizard.

Assumptions: Forgejo is running via Docker Compose (from the previous workshop). You know your server's public IP address.

Steps:

  1. Access Forgejo in Browser: Open your web browser and navigate to http://<your_server_ip>:3000.

    • Replace <your_server_ip> with the actual public IP address of your server.
    • Troubleshooting: If the page doesn't load:
      • Verify the Forgejo container is running (docker ps).
      • Check the Forgejo logs (docker logs forgejo_server) for errors.
      • Ensure your server's firewall allows incoming connections on port 3000 (e.g., using sudo ufw allow 3000/tcp if using UFW).
      • Confirm you are using http and not https.
  2. You should see the "Install" page. Take a moment to review the fields.

  3. Configure Database:

    • Verify Database Type is set to SQLite3.
    • Leave the Path as the default (data/forgejo.db).
  4. Configure General Settings:

    • Site Title: Enter a descriptive title (e.g., "My Personal Forgejo").
    • Repository Root Path: Leave the default /data/git/repositories.
    • Run As Username: Leave the default git.
    • SSH Server Domain: Enter your server's IP address for now (e.g., 192.0.2.100). If you have a domain pointing to this IP, you can use that.
    • SSH Server Port: Change this to 2222 (matching our docker-compose.yml host port).
    • Forgejo Base URL: Enter http://<your_server_ip>:3000/. Make sure this is correct and includes the http:// prefix and the :3000 port. Trailing slash is important.
    • HTTP Server Port: Leave the default 3000.
  5. Configure Administrator Account:

    • Scroll down to the "Administrator Account Settings" section (it might be under "Optional Settings").
    • Enter a Username (e.g., admin or your preferred username).
    • Enter a strong Password and confirm it.
    • Enter your Email Address. This is important for account recovery and notifications if email is configured later.
  6. Review Optional Settings: Glance through other settings like "Email Service Settings" and "Server and Third-Party Service Settings". You can leave these as defaults for now and configure them later if needed. Specifically, ensure "Disable Self-registration" is checked unless you intentionally want anyone to be able to sign up. For a personal server, disabling registration is generally safer.

  7. Install Forgejo: Click the "Install Forgejo" button at the bottom.

  8. Wait for Installation: Forgejo will save the configuration to app.ini, set up the database (the SQLite file), and prepare the instance. This might take a few moments.

  9. Login: Once complete, you should be redirected to the login page or the main dashboard if automatically logged in. Log in using the administrator username and password you just created.

Conclusion: You have successfully configured your Forgejo instance! You can now access the dashboard, create users, organizations, and repositories. The core setup is complete. Remember the key settings like the Base URL and SSH Port, as they are crucial for accessing your repositories.

Core Git Operations with Forgejo

Now that Forgejo is running and configured, let's use it for its primary purpose: managing Git repositories. This involves interacting with Forgejo both through its web interface and using standard Git commands from your local development machine.

Workflow Overview:

  1. (Forgejo UI) Create a User (if needed, beyond the admin).
  2. (Forgejo UI) Create a Repository.
  3. (Local Machine) Clone the empty repository from Forgejo.
  4. (Local Machine) Add files, commit changes.
  5. (Local Machine) Push changes back to the Forgejo server.
  6. (Forgejo UI) View the pushed code, history, etc.

Key Concepts:

  • Repository: A project's collection of files and the entire history of changes. On Forgejo, each project gets its own repository.
  • Remote: In Git terminology, a remote is a pointer to another copy of the repository, typically on a server. When you clone from Forgejo, it automatically sets up a remote named origin pointing back to your Forgejo instance.
  • Clone URL: The address used by Git to connect to the remote repository. Forgejo provides HTTPS and SSH clone URLs.
    • HTTPS URL: Looks like http://<your_server_ip>:3000/YourUsername/YourRepoName.git. Usually requires your Forgejo username and password (or an access token) for authentication on push. Simpler to set up initially.
    • SSH URL: Looks like ssh://git@<your_server_ip_or_domain>:2222/YourUsername/YourRepoName.git. Uses SSH keys for authentication, generally considered more secure and convenient for frequent use (no password typing). Requires setting up SSH keys.

Using SSH Keys (Recommended):

To use the SSH clone URL, you need to:

  1. Generate an SSH Key Pair: If you don't already have one on your local development machine, use ssh-keygen.

    # On your local machine (not the server)
    ssh-keygen -t ed25519 -C "your_email@example.com"
    # Follow prompts: accept default file location (~/.ssh/id_ed25519), set a strong passphrase!
    

    • Explanation: ssh-keygen creates a private key (~/.ssh/id_ed25519) and a public key (~/.ssh/id_ed25519.pub). -t ed25519 specifies the modern and secure Ed25519 algorithm. -C adds a comment (usually your email) to the key. Protect your private key and use a passphrase!
  2. Add Public Key to Forgejo:

    • Log in to your Forgejo web UI.
    • Go to User Settings (click your profile picture in the top right -> Settings).
    • Navigate to the "SSH / GPG Keys" tab.
    • Click "Add Key".
    • Copy the entire content of your public key file (~/.ssh/id_ed25519.pub). You can display it using cat ~/.ssh/id_ed25519.pub on your local machine.
    • Paste the public key content into the "Content" text area in Forgejo.
    • Give the key a recognizable "Key Name" (e.g., "My Laptop").
    • Click "Add Key".

Now, when you perform Git operations using the SSH URL, Git will automatically use your private key to authenticate with Forgejo (which verifies it against the stored public key). If your key has a passphrase, you'll be prompted to enter it.

Workshop Creating Your First Repository and Pushing Code

Objective: Create a repository in Forgejo, clone it locally, make a change, and push it back using Git over SSH.

Assumptions:

  • Forgejo is running and configured.
  • You are logged into the Forgejo web UI as the administrator (or another user).
  • You have Git installed on your local development machine.
  • You have generated an SSH key pair locally and added the public key to your Forgejo user settings (as described above).
  • You know your server's IP address or domain name.

Steps:

  1. Create a New Repository in Forgejo:

    • In the Forgejo web UI, click the "+" icon in the top right corner and select "New Repository".
    • Owner: Choose your username.
    • Repository Name: Enter my-first-project.
    • Description: (Optional) Add a short description, e.g., "My first repository on self-hosted Forgejo".
    • Visibility: Keep it "Private" for now unless you want it publicly accessible.
    • Initialize Repository: Check the box for "Initialize Repository with..." and select options like "Add .gitignore" (choose a relevant template, e.g., "Python" or leave as "None") and "Add License" (e.g., "MIT License"). This creates the repo with some initial files, which is common practice.
    • Click "Create Repository".
  2. Get the SSH Clone URL:

    • On the repository page in Forgejo, click the green "Code" button (or similar, might just show the URLs).
    • Make sure "SSH" is selected.
    • Copy the SSH URL provided. It should look like: ssh://git@<your_server_ip_or_domain>:2222/YourUsername/my-first-project.git (replace placeholders with your actual values, including port 2222).
  3. Clone the Repository Locally:

    • Open a terminal or command prompt on your local development machine.
    • Navigate to the directory where you want to store your projects.
    • Run the git clone command with the copied SSH URL:
      git clone ssh://git@<your_server_ip_or_domain>:2222/YourUsername/my-first-project.git
      
      • (First time connection): You might see a message like "The authenticity of host '[]:2222' can't be established... Are you sure you want to continue connecting (yes/no/[fingerprint])?". Type yes and press Enter. This adds the server's host key to your ~/.ssh/known_hosts file.
      • (SSH Key Passphrase): If your SSH key is protected by a passphrase, you will be prompted to enter it.
    • A new directory named my-first-project should be created, containing the files you initialized the repository with (.gitignore, LICENSE).
  4. Make Changes:

    • Navigate into the new repository directory:
      cd my-first-project
      
    • Create a new file:
      echo "Hello Forgejo!" > README.md
      
      • Explanation: Creates a file named README.md with the text "Hello Forgejo!".
  5. Stage and Commit Changes:

    • Check the status:
      git status
      
      • Explanation: Shows untracked files (README.md).
    • Stage the new file:
      git add README.md
      # Or stage all changes: git add .
      
      • Explanation: Tells Git to track changes in README.md for the next commit.
    • Commit the changes:
      git commit -m "Add initial README file"
      
      • Explanation: Records the staged changes as a snapshot in the project's history. The -m flag provides a descriptive commit message.
  6. Push Changes to Forgejo:

    • Push the committed changes from your local main (or master) branch to the origin remote (your Forgejo server):
      git push origin main # Or 'master' if that's your default branch name
      
      • (SSH Key Passphrase): You might be prompted for your SSH key passphrase again.
    • You should see output indicating the push was successful, transferring objects to the remote server.
  7. Verify in Forgejo Web UI:

    • Go back to your browser and refresh the my-first-project repository page on your Forgejo instance.
    • You should now see the README.md file listed, and the latest commit message ("Add initial README file") should be visible. You can click on "Commits" to see the history.

Conclusion: You have successfully completed the fundamental Git workflow with your self-hosted Forgejo server: creating a repository, cloning it, making local changes, and pushing them back to the server using SSH authentication. You can now repeat steps 4-6 whenever you make further changes to your project.

2. Intermediate Configuration and Management

With the basics covered, this section delves into more advanced configurations to make your Forgejo instance more robust, secure, and production-ready. We'll cover setting up HTTPS, using a more powerful database, managing users effectively, and implementing backup strategies.

Reverse Proxy and HTTPS Setup

Running Forgejo directly exposed on port 3000 via HTTP is acceptable for initial testing or strictly internal networks, but highly discouraged for any instance accessible over the internet. Exposing it directly has several drawbacks:

  • No Encryption: All traffic, including login credentials and code, is sent in plain text (HTTP), making it vulnerable to eavesdropping.
  • Non-Standard Port: Using port 3000 requires users to remember and type the port number in the URL. Standard web traffic uses port 80 (HTTP) and 443 (HTTPS).
  • Limited Features: Directly exposing Forgejo prevents easy implementation of features like caching, rate limiting, or serving multiple websites from the same IP address.

A reverse proxy solves these problems. It's a server (like Nginx or Apache) that sits in front of Forgejo, receives incoming web requests, and forwards them to the Forgejo application running internally.

Benefits of using a Reverse Proxy (e.g., Nginx):

  1. HTTPS/SSL Termination: The reverse proxy handles the SSL/TLS encryption and decryption. Users connect to the proxy via HTTPS (port 443), and the proxy communicates with Forgejo internally (e.g., over HTTP on port 3000). This secures the connection from the user to your server.
  2. Standard Ports: Users can access Forgejo via standard ports (e.g., https://git.yourdomain.com), without needing :3000.
  3. Load Balancing: While less relevant for a single Forgejo instance, reverse proxies can distribute traffic across multiple backend application servers for high availability and performance.
  4. Caching: Can cache static assets (CSS, JS, images) to speed up loading times and reduce load on Forgejo.
  5. Security: Can implement additional security measures like rate limiting, blocking malicious IPs, or adding security headers.
  6. Hosting Multiple Sites: Allows you to run Forgejo alongside other web applications on the same server, using the same IP address but different domain names (virtual hosts).

Using Nginx as a Reverse Proxy:

Nginx is a popular, high-performance web server and reverse proxy. Here's a typical configuration snippet for proxying requests to our Dockerized Forgejo instance:

# /etc/nginx/sites-available/forgejo.conf

server {
    listen 80;                     # Listen on port 80 for incoming HTTP requests
    server_name git.yourdomain.com;  # Replace with your actual domain name

    # Redirect all HTTP traffic to HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;          # Listen on port 443 for HTTPS, enable HTTP/2
    server_name git.yourdomain.com;  # Replace with your actual domain name

    # SSL Certificate configuration (using Let's Encrypt / Certbot)
    ssl_certificate /etc/letsencrypt/live/git.yourdomain.com/fullchain.pem; # Path to your certificate
    ssl_certificate_key /etc/letsencrypt/live/git.yourdomain.com/privkey.pem; # Path to your private key
    include /etc/letsencrypt/options-ssl-nginx.conf; # Recommended SSL settings from Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;   # DH parameters from Certbot

    # Security Headers (Optional but recommended)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-Frame-Options DENY always;
    add_header X-XSS-Protection "1; mode=block" always;
    # add_header Content-Security-Policy "default-src 'self'; ..." # More complex, requires tuning

    # Increase max body size for large pushes/uploads
    client_max_body_size 100m; # Adjust as needed (e.g., 500m for larger LFS files)

    location / {
        proxy_pass http://127.0.0.1:3000; # Forward requests to Forgejo running on localhost:3000

        # Required proxy headers for Forgejo
        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;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port $server_port;

        # WebSocket support (needed for live updates in UI)
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    # Optional: Improve static file serving (if assets are outside /data)
    # location ~ ^/(css|js|img|fonts)/ {
    #    root /path/to/forgejo/public; # Adjust if needed
    #    expires 1h;
    # }
}

Explanation:

  • HTTP Server Block (port 80): Catches all incoming HTTP requests for git.yourdomain.com and issues a permanent redirect (301) to the HTTPS version.
  • HTTPS Server Block (port 443):
    • listen 443 ssl http2;: Listens for HTTPS traffic, enables SSL/TLS, and enables HTTP/2 for better performance.
    • server_name: Specifies the domain this block applies to.
    • ssl_certificate, ssl_certificate_key, include, ssl_dhparam: These lines configure SSL using certificates obtained from Let's Encrypt via Certbot (we'll cover this in the workshop).
    • add_header ...: Adds various security headers to enhance protection against common web vulnerabilities (HSTS, clickjacking, XSS).
    • client_max_body_size: Sets the maximum size for client request bodies, important for allowing large Git pushes or file uploads. Default is often 1MB, which is too small.
    • location / { ... }: Defines how to handle requests for the root and any sub-paths.
      • proxy_pass http://127.0.0.1:3000;: The core directive. It forwards the request to http://127.0.0.1:3000, which is where our Dockerized Forgejo instance is listening (we mapped host port 3000 to container port 3000).
      • proxy_set_header ...: These headers pass crucial information from the original client request to Forgejo (like the original host domain, client IP, and protocol used - HTTPS), allowing Forgejo to generate correct URLs and log accurate information. These are essential for Forgejo to function correctly behind a reverse proxy.
      • WebSocket headers (Upgrade, Connection): Necessary for real-time features in the Forgejo UI.

Let's Encrypt and Certbot:

Let's Encrypt is a free, automated, and open Certificate Authority (CA). Certbot is a client tool that simplifies obtaining and renewing Let's Encrypt SSL certificates. When configured with the Nginx plugin, Certbot can automatically obtain certificates, configure Nginx to use them, and set up automatic renewal.

Forgejo Configuration Changes:

After setting up the reverse proxy for HTTPS, you need to update Forgejo's configuration (app.ini) to reflect the new public URL:

  • [server] section:
    • ROOT_URL = https://git.yourdomain.com/ (Update to your HTTPS domain)
    • HTTP_PORT = 3000 (This remains the internal port Forgejo listens on)
    • PROTOCOL = http (Because Nginx talks to Forgejo over plain HTTP internally. Nginx handles the external HTTPS).
    • SSH_DOMAIN = git.yourdomain.com (Update domain if necessary)
    • SSH_PORT = 2222 (Remains the external SSH port)

You'll need to restart the Forgejo container after editing app.ini.

Workshop Securing Forgejo with Nginx and Let's Encrypt

Objective: Configure Nginx as a reverse proxy for Forgejo, obtain a Let's Encrypt SSL certificate, and update Forgejo's configuration.

Assumptions:

  • Forgejo is running via Docker Compose on ports 3000 (HTTP) and 2222 (SSH).
  • You have a registered domain name (e.g., git.yourdomain.com) with DNS properly configured: an A record pointing your domain to your server's public IP address.
  • Ports 80 and 443 are open on your server's firewall (sudo ufw allow 80/tcp, sudo ufw allow 443/tcp). Port 3000 might no longer need to be open externally once Nginx is set up, but internal access from Nginx must work.
  • You are logged into your server with sudo privileges.

Steps:

  1. Install Nginx:

    sudo apt update
    sudo apt install -y nginx
    

    • Explanation: Installs the Nginx web server.
  2. Install Certbot and Nginx Plugin:

    sudo apt install -y certbot python3-certbot-nginx
    

    • Explanation: Installs Certbot and its plugin for automatically configuring Nginx.
  3. Create Nginx Configuration File for Forgejo:

    • Use nano or another editor to create the Nginx configuration file:
      sudo nano /etc/nginx/sites-available/forgejo.conf
      
    • Paste the Nginx configuration example provided in the theory section above into this file.
    • Crucially: Replace all instances of git.yourdomain.com with your actual domain name. Adjust client_max_body_size if needed.
    • Note: Initially, the SSL configuration lines (ssl_certificate, ssl_certificate_key, etc.) won't work because the certificates don't exist yet. We'll let Certbot handle this. You can leave them commented out for now or let Certbot add/modify them. For simplicity here, we'll assume you paste the full config, and Certbot will adjust it.
    • Save and close the file (Ctrl+X, Y, Enter in nano).
  4. Enable the Nginx Site Configuration:

    sudo ln -s /etc/nginx/sites-available/forgejo.conf /etc/nginx/sites-enabled/
    # Remove the default Nginx welcome page config if it exists
    sudo rm /etc/nginx/sites-enabled/default
    

    • Explanation: Creates a symbolic link from sites-available (where configs are stored) to sites-enabled (where Nginx looks for active configs). Removing the default config prevents conflicts.
  5. Test Nginx Configuration:

    sudo nginx -t
    

    • Explanation: Checks the Nginx configuration files for syntax errors. If you pasted the SSL lines already, this might initially fail because the certificate files don't exist yet - this is expected at this stage if you included those lines. If it reports other syntax errors, fix them in forgejo.conf before proceeding.
  6. Obtain SSL Certificate using Certbot:

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

    • Explanation:
      • certbot: Runs the Certbot client.
      • --nginx: Specifies using the Nginx plugin to automate configuration.
      • -d git.yourdomain.com: Specifies the domain name(s) to obtain certificates for. Replace with your domain.
    • Follow Prompts:
      • Enter your email address (for renewal notices).
      • Agree to the Terms of Service.
      • Choose whether to share your email (optional).
      • Certbot will detect the domain in your Nginx config. It will ask if you want it to automatically configure Nginx for HTTPS, including setting up the redirect from HTTP to HTTPS. Choose the option to redirect (usually option 2).
    • If successful, Certbot will obtain the certificate, place it in /etc/letsencrypt/live/git.yourdomain.com/, modify your /etc/nginx/sites-available/forgejo.conf to include the necessary SSL directives (or uncomment/correct them if you pasted them earlier), and reload Nginx.
  7. Verify Nginx Reloaded: Check Nginx status.

    sudo systemctl status nginx
    

    • It should show as active (running). If Certbot didn't reload it, run sudo systemctl reload nginx.
  8. Update Forgejo Configuration (app.ini):

    • Navigate to your Forgejo project directory on the host (where docker-compose.yml is).
    • Edit the app.ini file:
      nano forgejo/data/forgejo/conf/app.ini
      
    • Find the [server] section and make these changes (adjusting git.yourdomain.com):
      [server]
      PROTOCOL           = http  # Nginx handles external HTTPS
      DOMAIN             = git.yourdomain.com
      ROOT_URL           = https://git.yourdomain.com/
      HTTP_ADDR          = 0.0.0.0 # Listen on all interfaces inside container
      HTTP_PORT          = 3000
      ; ... other settings ...
      SSH_DOMAIN         = git.yourdomain.com # Update if using domain for SSH
      SSH_PORT           = 2222 # External SSH port remains the same
      ; ... LFS settings if applicable ...
      
    • Save and close the file.
  9. Restart Forgejo Container: Apply the configuration changes.

    # In the directory containing docker-compose.yml
    docker compose restart forgejo
    # Or: docker restart forgejo_server
    

  10. Test Access:

    • Open your browser and navigate to https://git.yourdomain.com (using HTTPS and your domain).
    • You should be automatically redirected from HTTP if you type http://....
    • The Forgejo login page should load securely (look for the padlock icon in the browser's address bar).
    • Log in and verify that repository clone URLs shown in the UI are now using the correct HTTPS and SSH domain names (e.g., https://git.yourdomain.com/User/repo.git and ssh://git@git.yourdomain.com:2222/User/repo.git).
    • Try cloning/pushing using the new HTTPS URL (it will likely prompt for your Forgejo username/password) and the updated SSH URL.

Conclusion: You have successfully placed Nginx as a reverse proxy in front of Forgejo, secured it with a free Let's Encrypt SSL certificate, and configured both Nginx and Forgejo to work together seamlessly. Your Forgejo instance is now accessible via a standard, secure HTTPS URL. Certbot will typically handle automatic renewal of the certificate. You can test renewal with sudo certbot renew --dry-run.

Database Configuration Deep Dive

While SQLite is convenient for single-user or very small instances, it has limitations, particularly concerning concurrent write operations. As your Forgejo usage grows (more users, more frequent pushes, CI/CD activity), performance can degrade because SQLite locks the entire database file during writes.

For better scalability, performance, and robustness, using a dedicated relational database server like PostgreSQL or MySQL/MariaDB is highly recommended. PostgreSQL is often favored in the Forgejo/Gitea community for its reliability and feature set.

Why Use PostgreSQL?

  • Concurrency: Handles many simultaneous read and write operations efficiently using row-level locking, significantly improving performance under load compared to SQLite's database-level locking.
  • Scalability: Designed to handle large datasets and high transaction volumes gracefully.
  • Robustness: Offers features like Point-in-Time Recovery (PITR), replication, and advanced backup options.
  • Data Integrity: Enforces stricter data type checking and constraints.

Setting up PostgreSQL for Forgejo:

  1. Install PostgreSQL: Install the PostgreSQL server package on your host machine (or run it as a separate Docker container).

    # On Debian/Ubuntu
    sudo apt update
    sudo apt install -y postgresql postgresql-contrib
    

  2. Create Database and User: Access the PostgreSQL command-line interface and create a dedicated database and user for Forgejo.

    sudo -u postgres psql
    

    • Inside the psql shell:
      -- Create a dedicated database for Forgejo
      CREATE DATABASE forgejo WITH ENCODING 'UTF8' LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8' TEMPLATE=template0;
      
      -- Create a dedicated user for Forgejo
      CREATE USER forgejo_user WITH PASSWORD 'YOUR_STRONG_PASSWORD'; -- Replace with a secure password!
      
      -- Grant all privileges on the forgejo database to the forgejo_user
      GRANT ALL PRIVILEGES ON DATABASE forgejo TO forgejo_user;
      
      -- Exit psql
      \q
      
    • Explanation:
      • sudo -u postgres psql: Connects to the psql shell as the default postgres superuser.
      • CREATE DATABASE forgejo ...: Creates a database named forgejo. Specifying UTF8 encoding and locale settings ensures proper handling of various character sets. TEMPLATE=template0 is often recommended for clean encoding setup.
      • CREATE USER forgejo_user ...: Creates a database user named forgejo_user. Choose a very strong password.
      • GRANT ALL PRIVILEGES ...: Gives the forgejo_user full permissions only on the forgejo database. This follows the principle of least privilege.
  3. Configure Forgejo (app.ini): Modify the [database] section in your forgejo/data/forgejo/conf/app.ini file. Stop Forgejo before editing.

    [database]
    DB_TYPE             = postgres
    HOST                = <database_host>:<database_port> # e.g., 127.0.0.1:5432 or docker_service_name:5432
    NAME                = forgejo
    USER                = forgejo_user
    PASSWD              = 'YOUR_STRONG_PASSWORD' # Use the password you set! Enclose in single quotes if it contains special characters.
    SSL_MODE            = disable # Change to 'require' or 'verify-full' if connecting over network with SSL
    LOG_SQL             = false # Set to true for debugging SQL queries (very verbose)
    CONNECT_STR         = # Generally leave empty unless needed for specific connection params
    PATH                = data/forgejo.db # This setting is ignored when DB_TYPE is not sqlite3
    

    • Explanation:
      • DB_TYPE: Set to postgres.
      • HOST: The address and port of the PostgreSQL server.
        • If PostgreSQL is running on the same host as the Docker daemon (but outside Docker), 127.0.0.1:5432 (default PostgreSQL port) is usually correct if Forgejo can reach the host network.
        • If PostgreSQL is running as another Docker container on the same Docker network (forgejo_net), use the service name defined in docker-compose.yml (e.g., db:5432).
      • NAME: The database name (forgejo).
      • USER: The database user (forgejo_user).
      • PASSWD: The password you created for forgejo_user. Protect this! Consider using Docker secrets for better security in production.
      • SSL_MODE: Controls SSL connection to the database. disable is fine for connections on localhost or within a trusted Docker network. Use require if connecting over an untrusted network.
  4. Restart Forgejo: After saving app.ini, restart the Forgejo container. It will attempt to connect to the PostgreSQL database. Check the logs (docker logs forgejo_server) to confirm a successful connection and potentially see database migration messages as Forgejo sets up its tables.

Running PostgreSQL in Docker:

Alternatively, you can manage PostgreSQL within your docker-compose.yml file. This keeps your entire Forgejo stack containerized.

# Example additions to docker-compose.yml

version: "3.8"

networks:
  forgejo_net:
    external: false

services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:7.0
    container_name: forgejo_server
    environment:
      - USER_UID=1000
      - USER_GID=1000
    networks:
      - forgejo_net
    volumes:
      - ./forgejo/data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      # Only expose Nginx ports now, not 3000 or 2222 directly (if using Nginx)
      # - "3000:3000" # Keep if needed for direct access or Nginx on different host
      - "2222:22"   # Keep SSH port mapping
    restart: unless-stopped
    depends_on: # Add dependency on the database service
      - db

  db: # Define the PostgreSQL service
    image: postgres:15 # Use a specific PostgreSQL version
    container_name: forgejo_postgres
    restart: unless-stopped
    environment:
      - POSTGRES_USER=forgejo_user # Set desired username
      - POSTGRES_PASSWORD=YOUR_STRONG_PASSWORD # Set desired password (use Docker secrets in prod!)
      - POSTGRES_DB=forgejo # Set desired database name
    networks:
      - forgejo_net
    volumes:
      - ./postgres/data:/var/lib/postgresql/data # Persist PostgreSQL data

volumes: # Define persistent Docker volume (alternative to bind mount) - less used here as bind mounts specified
  # postgres_data: # Example if using named volume instead of bind mount for PG
    # driver: local

Explanation of db service:

  • image: postgres:15: Uses the official PostgreSQL image, version 15.
  • container_name: Gives the container a specific name.
  • restart: unless-stopped: Ensures the database restarts automatically.
  • environment: Configures the PostgreSQL container on first run. It automatically creates the specified user, password, and database. Use strong passwords!
  • networks: Connects the database container to the same forgejo_net network as Forgejo.
  • volumes: Mounts ./postgres/data on the host to /var/lib/postgresql/data inside the container, ensuring the database data persists across container restarts.

If using this Docker Compose setup for PostgreSQL, the HOST in Forgejo's app.ini [database] section should be db:5432 (service name and default port). The forgejo service also now includes depends_on: [db] to ensure the database container starts before Forgejo attempts to connect.

Data Migration:

If you initially set up Forgejo with SQLite and want to switch to PostgreSQL, you will need to migrate your data. Forgejo includes a command-line tool for dumping data (forgejo dump) and potentially importing it, but migrating between database types can sometimes be complex. Often, the easiest path (if feasible) is to:

  1. Back up your repositories (the git-repositories directory).
  2. Set up the new PostgreSQL database.
  3. Configure Forgejo to use PostgreSQL (pointing to an empty database).
  4. Restart Forgejo; it will initialize the schema in PostgreSQL.
  5. Re-create users, organizations, etc.
  6. Use Forgejo's migration features (or manual pushes) to re-import your repositories from the backups or another source.

Consult the official Forgejo documentation for the most current recommendations on database migration.

Workshop Configuring Forgejo with PostgreSQL

Objective: Reconfigure a running Forgejo instance (or set up a new one) to use PostgreSQL running in a separate Docker container managed by Docker Compose.

Assumptions:

  • You are comfortable editing docker-compose.yml and app.ini.
  • You have Docker and Docker Compose installed.
  • You are in the forgejo-server directory created earlier.
  • Warning: This workshop assumes you are either starting fresh or willing to potentially lose existing Forgejo data (users, issues, non-repo data) if migrating from SQLite without a proper data migration procedure. We focus here on the connection setup. If preserving data is critical, research Forgejo's data dump/restore/migration tools first.

Steps:

  1. Stop Forgejo: If Forgejo is currently running, stop it.

    # In the forgejo-server directory
    docker compose down
    

    • Explanation: down stops and removes the containers defined in the docker-compose.yml, but leaves the volumes (like ./forgejo/data) intact.
  2. Modify docker-compose.yml:

    • Edit the docker-compose.yml file:
      nano docker-compose.yml
      
    • Add the db service definition for PostgreSQL and the depends_on section to the forgejo service, as shown in the "Running PostgreSQL in Docker" example above.
    • Choose a strong password for POSTGRES_PASSWORD and remember it.
    • (Optional) If using Nginx, you might remove the "3000:3000" port mapping from the forgejo service, as access should primarily be through Nginx. Keep the SSH port mapping ("2222:22").
    • Your complete docker-compose.yml should look similar to the example provided in the theory section, incorporating both forgejo and db services.
    • Save and close the file.
  3. Create PostgreSQL Data Directory: Create the host directory for the PostgreSQL volume.

    mkdir -p postgres/data
    # Optional: Set permissions if needed, though Docker often handles volume permissions adequately for official images.
    # sudo chown <some_user>:<some_group> postgres/data # Usually not required for postgres image
    

  4. Modify Forgejo app.ini for PostgreSQL:

    • Edit the app.ini file:
      nano forgejo/data/forgejo/conf/app.ini
      
    • Locate the [database] section.
    • Modify the settings as follows (using the user/pass/db defined in docker-compose.yml):
      [database]
      DB_TYPE             = postgres
      HOST                = db:5432  # Use the service name 'db' and default PG port
      NAME                = forgejo
      USER                = forgejo_user
      PASSWD              = 'YOUR_STRONG_PASSWORD' # Use the SAME password as in docker-compose.yml!
      SSL_MODE            = disable
      ; Remove or comment out the PATH line for SQLite
      ; PATH                = data/forgejo.db
      LOG_SQL             = false
      
    • Ensure DB_TYPE is postgres and HOST points to the service name db.
    • Save and close the file.
  5. Start the Stack: Launch both Forgejo and PostgreSQL using Docker Compose.

    docker compose up -d
    

    • Explanation: Docker Compose will create the forgejo_net network, start the db container (PostgreSQL), wait for it to initialize (it will create the user/db specified in environment variables), and then start the forgejo container. The depends_on ensures this order.
  6. Check Logs: Monitor the logs of both containers to ensure they start correctly and Forgejo connects to the database.

    docker compose logs -f forgejo
    # Press Ctrl+C to stop following forgejo logs
    
    docker compose logs -f db
    # Press Ctrl+C to stop following db logs
    

    • In the forgejo logs, look for messages indicating a successful connection to the PostgreSQL database and potentially messages about running database migrations (e.g., "Initializing ORM engine" followed by "Database schema migration finished").
    • In the db (PostgreSQL) logs, look for messages like "database system is ready to accept connections".
  7. Test Forgejo: Access your Forgejo instance via its web URL (e.g., https://git.yourdomain.com).

    • If this is a fresh setup after switching databases, you will likely be presented with the /install page again, unless a previous app.ini already exists. If you see the install page, fill it out again, ensuring the database section correctly reflects the PostgreSQL settings you just configured in app.ini. Forgejo should detect the existing app.ini and use those values, skipping the install page if the DB connection works.
    • Log in (you might need to recreate the admin user if starting fresh).
    • Try creating a repository or performing other actions to confirm functionality.

Conclusion: You have successfully configured your Forgejo stack to use PostgreSQL running as a separate Docker container. This setup provides better performance and scalability compared to SQLite. Remember to manage the PostgreSQL password securely and include the PostgreSQL data volume (./postgres/data) in your backup strategy.

User Management and Permissions

Forgejo provides flexible mechanisms for managing users, organizing them into groups (Organizations and Teams), and controlling access to repositories.

User Accounts:

  • Registration: Can be open (anyone can register), restricted (requires admin approval or email domain whitelist), or disabled (only admins can create users). Configured in app.ini ([service] section, DISABLE_REGISTRATION, ENABLE_CAPTCHA, REGISTER_EMAIL_CONFIRM, ALLOWED_EMAIL_DOMAINS) or via the Admin Panel (Site Administration -> Configuration -> Application Settings). For private instances, disabling registration is recommended.
  • Authentication: Supports local database authentication, LDAP, OAuth2 (e.g., GitHub, Google), SMTP, and PAM. Configurable via the Admin Panel (Site Administration -> Authentication Sources).
  • User Roles:
    • Regular User: Standard permissions. Can create repositories (personal or in orgs/teams they belong to), push/pull based on permissions.
    • Administrator: Full control over the instance via the Admin Panel (manage users, repositories, system settings, authentication sources).
    • Restricted: Limited permissions, cannot create repositories, organizations, etc. Useful for users who only need read access or issue tracking capabilities.
  • Admin Panel: Accessible only to administrators (usually via /admin). Provides tools to:
    • Create, edit, delete users.
    • Manage user permissions (promote to admin, restrict).
    • View system status, queues, cron tasks.
    • Modify instance configuration (app.ini settings via UI).
    • Manage organizations and repositories globally.

Organizations:

  • Act as containers for repositories and teams, typically representing a company, project group, or department.
  • Users can be members of multiple organizations.
  • Each organization has owners and members. Owners have full administrative control within the organization.
  • Repositories can belong to an organization instead of an individual user.

Teams:

  • Exist within organizations.
  • Group users together (e.g., "Developers", "Testers", "Documentation").
  • Permissions are assigned to teams per repository. This is the primary way to manage fine-grained access control within an organization.
  • Team Permissions Levels:
    • Read: Can view repository code, issues, wiki, releases. Can clone/pull.
    • Write: Read permissions + Can push code to branches (respecting branch protection rules), manage issues/labels/milestones, edit wiki, manage releases.
    • Admin: Write permissions + Can manage repository settings, collaborators, webhooks, deploy keys, and team access to the repository.
  • Team Types:
    • Owner Team: Automatically created (usually called "Owners"), has Admin permissions on all organization repositories. Organization owners are typically members.
    • Regular Teams: Created manually for specific permission groupings.

Repository Access Control:

Access to a repository is determined by a combination of factors:

  1. Repository Visibility: Public (accessible to anyone, including non-logged-in users) or Private (requires explicit permissions).
  2. User Ownership: The user who owns the repository has Admin access.
  3. Organization Ownership: If owned by an organization:
    • Organization Owners have Admin access (via the Owners team).
    • Teams the user belongs to might grant Read, Write, or Admin access.
  4. Collaboration: Individual users can be added directly as collaborators to a repository with specific permissions (Read, Write, Admin), bypassing the need for teams in simpler scenarios.

Best Practices:

  • For collaborative projects, create an Organization.
  • Create Teams within the organization based on roles or responsibilities.
  • Grant permissions to Teams on specific repositories rather than adding many individual collaborators. This simplifies management as users join or leave roles/teams.
  • Regularly review user accounts and permissions, especially administrator access.
  • Use strong passwords and encourage users to enable Two-Factor Authentication (2FA) if available/needed.

Workshop Setting Up an Organization and Team Permissions

Objective: Create an organization, invite users (or create new ones), create teams, and assign specific permissions to a repository within that organization.

Assumptions:

  • Forgejo is running and accessible.
  • You are logged in as an administrator.

Steps:

  1. Create New Users (if needed):

    • Navigate to the Admin Panel (Top right profile -> Site Administration).
    • Go to User Management -> User Accounts.
    • Click "Create User".
    • Fill in the details for at least two new users (e.g., dev1, dev2). Provide temporary passwords (users can change them later) and email addresses. Ensure "Activated" is checked. Leave "Administrator" unchecked.
    • Click "Create User" for each new user.
  2. Create an Organization:

    • Navigate back to the main dashboard (click the Forgejo logo).
    • Click the "+" icon in the top right corner and select "New Organization".
    • Organization Name: Enter university-project-x.
    • (Optional) Fill in description, location, website.
    • Click "Create Organization".
    • You will be taken to the organization's page. Your admin user is automatically an owner.
  3. Add Members to the Organization (Optional but good practice):

    • On the organization page (university-project-x), go to the "Members" tab.
    • Start typing the username of one of the users you created (e.g., dev1) in the "Invite or add user" box. Select the user.
    • Click "Add Member". Repeat for dev2.
    • By default, they are added as "Members". You can change roles later if needed (e.g., make someone an Owner).
  4. Create Teams:

    • On the organization page, go to the "Teams" tab.
    • You'll see the default "Owners" team.
    • Click "New Team".
    • Team Name: developers.
    • Description: (Optional) "Core development team".
    • Permissions: Choose Write (This sets the default permission level this team gets when added to new repos, but we'll override it per-repo).
    • Team Units: Select the sections the team should have access to (Code, Issues, Pull Requests, etc.). Defaults are usually fine.
    • Click "Create Team".
    • Click "New Team" again.
    • Team Name: reviewers.
    • Description: (Optional) "Code reviewers".
    • Permissions: Choose Read.
    • Click "Create Team".
  5. Add Members to Teams:

    • Go back to the "Teams" tab.
    • Click on the developers team name.
    • In the "Add member" box, type dev1 and add them.
    • Go back to the "Teams" tab.
    • Click on the reviewers team name.
    • In the "Add member" box, type dev2 and add them.
  6. Create a Repository within the Organization:

    • Navigate back to the university-project-x organization page.
    • Click the "New Repository" button.
    • Owner: Ensure university-project-x is selected.
    • Repository Name: project-backend.
    • Visibility: Set to Private.
    • Initialize it with a README or leave it empty.
    • Click "Create Repository".
  7. Assign Team Permissions to the Repository:

    • On the project-backend repository page, go to "Settings".
    • Navigate to the "Collaborators & Teams" tab (or similar name, might be just "Collaborators").
    • Scroll down to the "Teams" section.
    • In the "Add Team" dropdown, select the developers team. Choose Write permission from the adjacent dropdown. Click "Add Team".
    • In the "Add Team" dropdown, select the reviewers team. Choose Read permission. Click "Add Team".
  8. Verify Permissions (Simulate User Login):

    • Log out from your administrator account.
    • Log in as dev1 (the developer).
      • Navigate to the university-project-x organization.
      • Access the project-backend repository.
      • Verify dev1 can see the repository content.
      • Verify dev1 can see the clone URLs (HTTPS/SSH). They should be able to clone and push (Write access).
      • Check repository "Settings" - dev1 should not have access to most settings, only those allowed by Write permissions.
    • Log out from dev1.
    • Log in as dev2 (the reviewer).
      • Navigate to the university-project-x organization.
      • Access the project-backend repository.
      • Verify dev2 can see the repository content and clone URLs.
      • Try simulating a push (or look for UI elements) - dev2 should not be able to push code (Read access only). They should be able to view code, issues, etc.
      • Check repository "Settings" - dev2 should have very limited or no access.

Conclusion: You have successfully structured collaboration using an organization and teams. The developers team has Write access to the project-backend repository, allowing them to contribute code, while the reviewers team has Read access, suitable for code review or viewing progress. This demonstrates a scalable way to manage permissions for multiple users and projects.

Backup and Restore Strategies

Your Forgejo instance contains critical data: Git repositories, database information (users, issues, permissions, metadata), configuration files, and potentially attachments or LFS objects. Losing this data can be catastrophic. Implementing a robust backup and restore strategy is non-negotiable for any self-hosted service.

What Needs Backing Up?

Assuming our Docker setup with bind mounts (./forgejo/data and potentially ./postgres/data):

  1. Forgejo Configuration: The app.ini file (./forgejo/data/forgejo/conf/app.ini).
  2. Forgejo Data Directory: The entire /data volume mapped to ./forgejo/data on the host. This includes:
    • Git Repositories: Located in /data/git/repositories (host: ./forgejo/data/git/repositories). This is often the largest part.
    • Database (if SQLite): The forgejo.db file (e.g., ./forgejo/data/data/forgejo.db if default path used).
    • Attachments: Files uploaded to issues/releases (./forgejo/data/data/attachments).
    • LFS Objects: If using Large File Storage (./forgejo/data/lfs).
    • Avatars, Indices, Logs: Other miscellaneous but potentially useful data.
  3. Database (if PostgreSQL/MySQL): A separate dump of the database. Simply copying the data files (./postgres/data) while the database is running is not reliable and can lead to corrupted backups. You need to use the database's specific dump tool (e.g., pg_dump for PostgreSQL).
  4. (Optional but Recommended) Docker Compose File: Your docker-compose.yml file, which defines how your services are configured.
  5. (Optional but Recommended) Reverse Proxy Configuration: Your Nginx/Apache configuration files.

Backup Strategy:

The key challenge is ensuring consistency, especially between the database and the repository files. A Git push, for example, updates both the repository files on disk and related metadata in the database. Backing them up at slightly different times could lead to inconsistencies.

Recommended Approach (Offline Backup):

This is the safest method to guarantee consistency.

  1. Stop Forgejo: Prevent any new writes to the repositories or database.
    # In the directory with docker-compose.yml
    docker compose stop forgejo
    # Or use 'down' if you also need to stop the DB container for its backup
    # docker compose down
    
  2. Backup Database:
    • PostgreSQL (running host or separate container): Use pg_dump.
      # If PG running on host:
      sudo -u postgres pg_dump -U forgejo_user -Fc forgejo > forgejo_backup_$(date +%Y%m%d_%H%M%S).pgdump
      # If PG running in Docker container named 'forgejo_postgres':
      docker exec forgejo_postgres pg_dump -U forgejo_user -Fc forgejo > forgejo_backup_$(date +%Y%m%d_%H%M%S).pgdump
      
      • -U forgejo_user: Specifies the database user.
      • -Fc: Specifies the custom compressed dump format (good for consistency and size).
      • forgejo: The name of the database.
      • > ... .pgdump: Redirects the output to a timestamped backup file. You might be prompted for the forgejo_user password unless you've set up passwordless access (e.g., via .pgpass).
    • SQLite: Simply copy the database file (since Forgejo is stopped, the file is consistent).
      cp ./forgejo/data/data/forgejo.db ./forgejo_backup_$(date +%Y%m%d_%H%M%S).db
      
  3. Backup Forgejo Data Directory: Use tar or rsync to archive the ./forgejo/data directory. rsync can be efficient for subsequent backups as it only copies changes.

    # Using tar (creates a single compressed archive)
    tar czf forgejo_data_backup_$(date +%Y%m%d_%H%M%S).tar.gz ./forgejo/data
    
    # Using rsync (copies to a backup location, good for incremental)
    # rsync -avz --delete ./forgejo/data/ /path/to/forgejo_backup_location/
    

    • tar czf: Create (c), gzip compress (z), verbose (v, optional), file (f).
    • rsync -avz --delete: Archive mode (a), verbose (v), compress (z), delete files in destination that don't exist in source (--delete).
  4. Backup Configuration Files: Copy docker-compose.yml, Nginx configs, etc.

    cp docker-compose.yml /path/to/backups/
    cp /etc/nginx/sites-available/forgejo.conf /path/to/backups/
    # etc.
    

  5. Restart Forgejo:

    docker compose up -d # Or 'start' if you only stopped services
    

  6. Store Backups Securely: Move the backup files (.pgdump or .db, .tar.gz) to a separate, secure location (different server, cloud storage, offline media).

Backup Frequency: Depends on how critical the data is and how frequently it changes. Daily backups are common for active instances.

Online Backup (Caution):

Forgejo's built-in forgejo dump command can create backups while the instance is running. However, it might temporarily lock the instance, and consistency between a very large filesystem backup and the database dump during high activity can still be a concern. Test thoroughly.

# Run inside the Forgejo container
docker exec forgejo_server sh -c 'cd /data && forgejo dump -c /data/forgejo/conf/app.ini --file /data/forgejo-backup-$(date +%Y%m%d_%H%M%S).zip'
# Then copy the zip file out of the container/volume:
# docker cp forgejo_server:/data/forgejo-backup-....zip /path/to/backups/

Restore Procedure:

Restoring requires reversing the backup process. Always test your restore procedure periodically!

  1. Prepare Environment: Set up a server with Docker, Nginx (if used), etc. Copy the docker-compose.yml and Nginx configs back.
  2. Stop Services: Ensure no Forgejo/database containers are running (docker compose down).
  3. Restore Forgejo Data Directory: Extract the forgejo_data...tar.gz archive into the correct location (./forgejo/data) or use rsync to copy back from the backup location. Ensure file permissions and ownership are correct (might need chown if user IDs differ).
    # Using tar
    # Ensure ./forgejo/data exists and is empty or doesn't exist
    tar xzf /path/to/backups/forgejo_data_backup_....tar.gz -C ./
    # Ensure correct ownership if needed: sudo chown -R 1000:1000 ./forgejo/data
    
    # Using rsync
    # rsync -avz /path/to/forgejo_backup_location/ ./forgejo/data/
    
  4. Restore Database:
    • PostgreSQL:
      1. Start only the PostgreSQL container (docker compose up -d db).
      2. Create an empty database (if it doesn't exist from the container startup). You might need to drop the existing one first.
        # Connect to container's psql
        docker exec -it forgejo_postgres psql -U forgejo_user -d postgres
        # Inside psql: DROP DATABASE forgejo; CREATE DATABASE forgejo WITH ... (use original CREATE command); \q
        
      3. Restore the dump file:
        cat /path/to/backups/forgejo_backup_....pgdump | docker exec -i forgejo_postgres pg_restore -U forgejo_user -d forgejo -v
        
        • pg_restore: Command to restore PostgreSQL dumps. -d forgejo specifies the target database. -v for verbose output.
    • SQLite: Copy the backed-up .db file back into the correct location (./forgejo/data/data/forgejo.db), overwriting any existing file.
  5. Start Forgejo:
    docker compose up -d forgejo # Start only Forgejo, DB should be running
    # Or: docker compose up -d # Start everything if not already running
    
  6. Test: Access the Forgejo instance and verify that repositories, users, issues, and settings are restored correctly. Check clone/push functionality.

Workshop Performing a Backup and Simulated Restore

Objective: Perform a complete backup of the running Forgejo instance (using PostgreSQL) and simulate a restore to verify the backup integrity.

Assumptions:

  • Forgejo is running with PostgreSQL, managed by Docker Compose (forgejo and db services).
  • You are in the forgejo-server directory containing docker-compose.yml, ./forgejo/data, and ./postgres/data.
  • You have created at least one repository and user in Forgejo.

Steps:

Part 1: Backup

  1. Create a Backup Directory:

    mkdir ../forgejo-backups
    

    • Creates a directory outside the main forgejo-server directory to store backups.
  2. Stop Services: Stop Forgejo and the database to ensure consistency.

    docker compose down
    

  3. Backup PostgreSQL Database:

    • We need the db container running temporarily to execute pg_dump. Start only the database:
      docker compose up -d db
      sleep 5 # Give PG a moment to start
      
    • Execute pg_dump via docker exec:
      docker exec forgejo_postgres pg_dump -U forgejo_user -Fc forgejo > ../forgejo-backups/forgejo_db_backup_$(date +%Y%m%d_%H%M%S).pgdump
      
      • You might be prompted for the forgejo_user password (the one set in docker-compose.yml).
    • Stop the database container again:
      docker compose stop db # Use stop, not down, to keep the container for later if needed
      
  4. Backup Forgejo Data Directory: Archive the ./forgejo/data directory.

    tar czf ../forgejo-backups/forgejo_data_backup_$(date +%Y%m%d_%H%M%S).tar.gz ./forgejo/data
    

  5. Backup Configuration Files:

    cp docker-compose.yml ../forgejo-backups/
    # Copy Nginx config if applicable
    # cp /etc/nginx/sites-available/forgejo.conf ../forgejo-backups/
    

  6. (Optional Cleanup before Restore Simulation): To simulate a disaster, let's rename the original data directories. Be careful here!

    # Ensure containers are stopped (docker compose ps should be empty or show exited containers)
    mv ./forgejo/data ./forgejo/data_OLD
    mv ./postgres/data ./postgres/data_OLD
    

    • This makes it seem like the original data is lost.

Part 2: Restore Simulation

  1. Prepare Restore Area: Ensure the target directories exist but are empty.

    mkdir -p ./forgejo/data
    mkdir -p ./postgres/data
    

  2. Restore Forgejo Data Directory: Extract the data archive.

    # Find the data backup file name
    ls ../forgejo-backups/forgejo_data_backup_*.tar.gz
    # Extract (replace ... with the actual filename)
    tar xzf ../forgejo-backups/forgejo_data_backup_....tar.gz -C ./
    # Check ownership (adjust if your host UID/GID is not 1000)
    ls -ld ./forgejo/data
    # If needed: sudo chown -R 1000:1000 ./forgejo/data
    

  3. Restore PostgreSQL Database:

    • Start only the database container. Since we moved ./postgres/data_OLD, it will initialize a fresh, empty data directory using the environment variables from docker-compose.yml.
      docker compose up -d db
      echo "Waiting for PostgreSQL to initialize..."
      sleep 15 # Wait for PG to initialize the new empty DB
      
    • Verify the empty database exists:
      docker exec -it forgejo_postgres psql -U forgejo_user -l
      # You should see the 'forgejo' database listed, but it will be empty.
      
    • Restore the dump file:
      # Find the DB backup file name
      ls ../forgejo-backups/forgejo_db_backup_*.pgdump
      # Restore (replace ... with actual filename)
      cat ../forgejo-backups/forgejo_db_backup_....pgdump | docker exec -i forgejo_postgres pg_restore -U forgejo_user -d forgejo -v
      
      • Look for successful restore messages and no major errors.
  4. Start Forgejo Service:

    docker compose up -d forgejo
    

  5. Verify Restoration:

    • Check container logs: docker compose logs forgejo. Look for successful startup and connection to the restored database.
    • Access Forgejo via your browser (https://git.yourdomain.com).
    • Log in using your original administrator or user account.
    • Verify that your repositories, users, organization (university-project-x), issues (if any), and settings are present and appear as they did before the backup.
    • Try cloning a repository.
  6. Cleanup (Optional): Once you've confirmed the restore was successful, you can remove the _OLD directories if you wish.

    # Be absolutely sure the restore worked before doing this!
    # sudo rm -rf ./forgejo/data_OLD
    # sudo rm -rf ./postgres/data_OLD
    
    You can also stop the containers: docker compose down.

Conclusion: You have successfully performed an offline backup of your Forgejo instance (configuration, file data, and PostgreSQL database) and simulated a restore process. This exercise highlights the critical steps involved and provides confidence that your backups are usable. Remember to automate backups and store them securely off-site. Regularly testing your restore procedure is essential.

3. Advanced Topics and Integrations

This section explores more advanced features and configurations for your Forgejo instance, including automating tasks with CI/CD, customizing the look and feel, enhancing security further, and migrating from other platforms.

Built-in CI/CD with Forgejo Actions

Forgejo includes a built-in Continuous Integration/Continuous Deployment (CI/CD) system called Forgejo Actions, which is largely compatible with GitHub Actions. This allows you to automate tasks like building code, running tests, checking for code style violations, and deploying applications directly from your Forgejo instance whenever code changes are pushed or pull requests are created.

Core Concepts:

  • Workflow: An automated process defined in a YAML file located in your repository under .forgejo/workflows/. A repository can have multiple workflows.
  • Event: A specific activity that triggers a workflow, such as push, pull_request, release, schedule, etc.
  • Job: A set of steps that execute on the same runner. Workflows can contain one or more jobs, which can run sequentially or in parallel.
  • Step: An individual task within a job. Steps can run shell commands or use pre-built Actions (reusable units of code).
  • Runner (act_runner): An application that listens for available jobs from your Forgejo instance, executes them, and reports the results back. You need to install and register at least one runner for Actions to work. Runners can be installed on the same server as Forgejo or on separate machines (physical, virtual, or Docker containers).
  • Action: A reusable piece of code that performs a specific task (e.g., actions/checkout@v3 to check out repository code, actions/setup-python@v4 to set up a Python environment). Forgejo Actions can use many actions designed for GitHub Actions.

How it Works:

  1. You commit a workflow YAML file (e.g., .forgejo/workflows/ci.yml) to your repository.
  2. An event occurs that the workflow is configured to listen for (e.g., a push to the main branch).
  3. Forgejo detects the event and queues the jobs defined in the workflow.
  4. A registered act_runner polls Forgejo, sees the available job, and picks it up.
  5. The runner executes the steps defined in the job (checking out code, running commands, etc.) usually within a temporary environment (often a Docker container).
  6. The runner sends logs and the final status (success/failure) back to Forgejo.
  7. Forgejo displays the workflow run status and logs in the "Actions" tab of the repository.

Setting up act_runner:

The act_runner needs to be downloaded and run somewhere it can communicate with your Forgejo instance's URL.

  1. Download act_runner: Obtain the latest release binary for your runner's operating system and architecture from the act_runner releases page (usually linked from Forgejo documentation or found on Codeberg/GitHub).
  2. Register the Runner: Run the register command. It will ask for:
    • Forgejo Instance URL: Your Forgejo ROOT_URL (e.g., https://git.yourdomain.com/).
    • Registration Token: Obtain this from Forgejo: Site Administration -> Runners -> Create new runner -> Registration Token, OR from Organization Settings -> Runners, OR User Settings -> Runners. Tokens determine the scope (instance, organization, or user level). Instance runners can run jobs for any repository; org/user runners are restricted.
    • Runner Name: A descriptive name (e.g., my-local-runner).
    • Labels (Optional): Tags used to direct jobs to specific runners (e.g., linux,docker,python). Workflows can specify required labels.
    • This creates a .runner configuration file in the act_runner directory.
  3. Run the Runner: Start the runner daemon.
    ./act_runner daemon
    
    • It's recommended to run act_runner as a systemd service or within a Docker container for background operation and management.

Example Workflow (.forgejo/workflows/greet.yml):

name: Greeting Workflow

# Controls when the workflow will run
on:
  push: # Trigger on every push event
    branches: [ main ] # Only for pushes to the main branch
  pull_request: # Trigger on pull request events
    branches: [ main ] # Only for PRs targeting the main branch

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "greet"
  greet_job:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest # Use a runner with the 'ubuntu-latest' label (common default)

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Step 1: Print a simple greeting
      - name: Say Hello
        run: echo "Hello, Forgejo Actions!"

      # Step 2: Print the branch or tag ref that triggered the workflow
      - name: Show Ref
        run: echo "This workflow was triggered by ref: ${{ github.ref }}"

      # Step 3: Check out repository code (uses a pre-built Action)
      - name: Check out code
        uses: actions/checkout@v3 # Use the standard checkout action

      # Step 4: List files in the checked-out directory
      - name: List Files
        run: ls -la

Explanation:

  • name: The name displayed in the Forgejo UI.
  • on: Defines the trigger events (push to main, pull_request targeting main).
  • jobs: Contains the job definitions.
    • greet_job: The ID of the job.
      • runs-on: ubuntu-latest: Specifies that this job needs a runner capable of running "ubuntu-latest" tasks (often means Linux, potentially with Docker). Runners are often labeled this way by default or via configuration.
      • steps: The sequence of tasks.
        • name: A descriptive name for the step shown in the UI.
        • run: Executes a shell command. github.ref is an example of accessing context variables.
        • uses: actions/checkout@v3: Uses a pre-defined action to perform a common task (checking out the repository code into the runner's workspace).

When this file is pushed to the main branch of a repository on your Forgejo instance (and you have a runner registered and running), the workflow will automatically execute.

Workshop Creating a Simple CI Pipeline

Objective: Create a simple Forgejo Actions workflow in your my-first-project repository that runs when code is pushed, download and register an act_runner, and observe the workflow execution.

Assumptions:

  • Your Forgejo instance is running and accessible via HTTPS (https://git.yourdomain.com).
  • You have the my-first-project repository created earlier.
  • You have git installed locally and can push to the repository.
  • You have shell access to a machine where you can run the act_runner (this can be the same server running Forgejo or your local machine, as long as it can reach the Forgejo URL).

Steps:

  1. Get Runner Registration Token:

    • Log in to Forgejo as an administrator.
    • Go to Site Administration -> Runners.
    • Under "Create new runner", find the "Registration token". Copy this token. We'll create an instance-level runner.
  2. Download and Prepare act_runner:

    • Go to the act_runner releases page (search for "forgejo act_runner releases" - it's likely hosted on codeberg.org, e.g., https://codeberg.org/forgejo/act_runner/releases).
    • Download the latest binary appropriate for the OS/Architecture where you will run the runner (e.g., act_runner-0.2.6-linux-amd64).
    • Open a terminal on the machine where you will run the runner.
    • Make the downloaded file executable:
      # Example for Linux amd64 download
      wget https://codeberg.org/forgejo/act_runner/releases/download/v0.2.6/act_runner-0.2.6-linux-amd64
      chmod +x act_runner-*-linux-amd64
      # Optional: Rename for convenience
      mv act_runner-*-linux-amd64 act_runner
      
  3. Register the Runner:

    • Run the registration command in the same directory as the act_runner binary:
      ./act_runner register --no-interactive --instance https://git.yourdomain.com --token YOUR_REGISTRATION_TOKEN --name my-first-runner --labels linux,docker
      
      • Replace https://git.yourdomain.com with your Forgejo URL.
      • Replace YOUR_REGISTRATION_TOKEN with the token you copied.
      • --no-interactive: Uses flags instead of prompts.
      • --name: Gives the runner a name.
      • --labels: Assigns labels. linux,docker are common useful labels.
    • This should create a .runner file in the current directory containing the runner's configuration. It might also create a .env file.
  4. Run the Runner:

    • Start the runner daemon process:
      ./act_runner daemon
      
    • The runner will start polling your Forgejo instance for jobs. Keep this terminal window open or run it in the background (e.g., using nohup ./act_runner daemon & or setting it up as a systemd service).
  5. Verify Runner in Forgejo:

    • Go back to the Forgejo UI -> Site Administration -> Runners.
    • You should see my-first-runner listed, likely with a green circle indicating it's active and polling.
  6. Create the Workflow File:

    • On your local machine, navigate into your cloned my-first-project repository directory.
    • Create the workflow directory:
      mkdir -p .forgejo/workflows
      
    • Create a new workflow file using a text editor:
      nano .forgejo/workflows/ci.yml
      
    • Paste the following content into the file:
      name: Basic Build and Test Simulation
      
      on:
        push:
          branches: [ main ] # Run on push to main branch
        pull_request:
          branches: [ main ] # Run on PRs targeting main branch
      
      jobs:
        build:
          runs-on: linux # Target our runner using the 'linux' label
      
          steps:
            - name: Checkout code
              uses: actions/checkout@v3
      
            - name: Simulate Build Step
              run: |
                echo "Starting build..."
                sleep 2 # Simulate time taken
                echo "Build successful!"
      
            - name: Simulate Test Step
              run: |
                echo "Running tests..."
                sleep 3 # Simulate time taken
                echo "All tests passed!"
      
            - name: Print Goodbye
              run: echo "Workflow finished."
      
    • Save and close the file (Ctrl+X, Y, Enter).
  7. Commit and Push the Workflow:

    • Stage the new workflow file:
      git add .forgejo/workflows/ci.yml
      
    • Commit the change:
      git commit -m "Add basic CI workflow"
      
    • Push to Forgejo:
      git push origin main
      
  8. Observe Workflow Execution:

    • Go to your my-first-project repository page in the Forgejo UI.
    • Click on the "Actions" tab.
    • You should see a new workflow run listed for the "Add basic CI workflow" commit. It might initially show as "Queued" or "Running".
    • Click on the workflow run name to see the details.
    • Click on the build job on the left.
    • You can expand each step ("Checkout code", "Simulate Build Step", etc.) to see the logs generated by the runner in real-time (or after completion).
    • The runner terminal (where you ran ./act_runner daemon) will also show output indicating it picked up and executed the job.
    • Once all steps complete successfully, the job and the overall workflow run should show a green checkmark (Success).

Conclusion: You have successfully set up Forgejo Actions! You added a workflow file to your repository, registered an act_runner, and triggered the workflow by pushing code. Forgejo automatically detected the push, assigned the job to your runner, and the runner executed the defined steps. This forms the foundation for automating builds, tests, and deployments within your Forgejo instance.

Customizing Forgejo's Appearance

Forgejo allows customization of its look and feel to better match your branding or personal preferences. This is primarily done by overriding templates and adding custom CSS or static assets.

The custom Directory:

The key to customization is the custom directory within Forgejo's data path. In our Docker setup, this corresponds to ./forgejo/data/forgejo/custom on the host. Forgejo checks this directory for overrides before using its default files.

Common Customizations:

  1. Custom CSS:

    • Create a CSS file: ./forgejo/data/forgejo/custom/public/css/theme-override.css
    • Add your custom CSS rules in this file. For example, to change the header background color:
      /* ./forgejo/data/forgejo/custom/public/css/theme-override.css */
      body .ui.top.menu {
        background-color: #2c3e50 !important; /* A dark blue-gray */
      }
      body .ui.top.menu .item {
          color: #ecf0f1 !important; /* Light gray text */
      }
      body .ui.top.menu .item:hover {
          background-color: #34495e !important; /* Slightly lighter on hover */
          color: #ffffff !important;
      }
      
    • Forgejo automatically detects and includes this CSS file. You might need to clear your browser cache or do a hard refresh (Ctrl+Shift+R or Cmd+Shift+R) to see changes. Sometimes a Forgejo restart (docker compose restart forgejo) helps ensure it picks up new custom files.
  2. Custom Logo:

    • Place your logo file (e.g., logo.svg or logo.png) in: ./forgejo/data/forgejo/custom/public/img/
    • Forgejo will automatically use custom/public/img/logo.svg if it exists, otherwise custom/public/img/logo.png, falling back to the default if neither custom file is found.
  3. Custom Favicon:

    • Place your favicon.png file in: ./forgejo/data/forgejo/custom/public/img/favicon.png
  4. Custom Templates (More Advanced):

    • You can override the HTML templates used to render pages. This allows for significant changes to layout and content.
    • Find the default template file you want to modify within the Forgejo source code (or a running container). For example, the overall page structure might be in templates/base/head.tmpl or templates/base/footer.tmpl.
    • Copy the original template file to the corresponding path within the custom directory on your host (e.g., ./forgejo/data/forgejo/custom/templates/base/footer.tmpl).
    • Edit the copied file in the custom directory.
    • Requires Restart: Template changes require restarting the Forgejo container (docker compose restart forgejo).
    • Caution: Overriding templates can make upgrading Forgejo more complex, as you may need to manually update your custom templates if the original templates change significantly in a new version. Use this sparingly and carefully.
  5. Custom Landing Page:

    • Create ./forgejo/data/forgejo/custom/templates/home.tmpl. Forgejo will use this instead of the default dashboard/landing page for non-logged-in users.
  6. Adding Static Files:

    • Files placed in ./forgejo/data/forgejo/custom/public/ (e.g., custom fonts, images used by your CSS) will be served under /assets/. For instance, custom/public/fonts/myfont.woff2 would be accessible at /assets/fonts/myfont.woff2.

Finding Default Files/Templates:

To know what to override, you can look inside the running Forgejo container or browse the Forgejo source code on Codeberg:

# List default templates inside the container
docker exec forgejo_server ls /usr/local/share/forgejo/templates/base/

# Copy a default template out to use as a base for customization
docker cp forgejo_server:/usr/local/share/forgejo/templates/base/footer.tmpl ./my_custom_footer.tmpl
# Then move ./my_custom_footer.tmpl to ./forgejo/data/forgejo/custom/templates/base/footer.tmpl and edit it.

Workshop Applying a Custom Theme (CSS Override)

Objective: Apply simple CSS customizations to change the header color and add a custom message to the footer of your Forgejo instance.

Assumptions:

  • Forgejo is running via Docker Compose.
  • You have access to the host directory mapped to Forgejo's data volume (./forgejo/data).

Steps:

  1. Create Custom CSS Directory:

    • Navigate to your forgejo-server directory on the host.
    • Create the necessary directories if they don't exist:
      mkdir -p ./forgejo/data/forgejo/custom/public/css
      
  2. Create Custom CSS File:

    • Use a text editor to create the override file:
      nano ./forgejo/data/forgejo/custom/public/css/theme-override.css
      
    • Paste the following CSS rules into the file:
      /* Custom Forgejo Theme */
      
      /* Change header background and text color */
      body .ui.top.fixed.menu { /* Target the top header menu */
        background: linear-gradient(to right, #11998e, #38ef7d); /* Green gradient */
        border-bottom: 1px solid #11998e;
      }
      
      body .ui.top.fixed.menu .item,
      body .ui.top.fixed.menu .item > .text { /* Target menu items and text within them */
        color: #ffffff !important; /* White text */
        opacity: 0.9;
      }
      
      body .ui.top.fixed.menu .item:hover {
        background-color: rgba(255, 255, 255, 0.1) !important; /* Slight white overlay on hover */
        opacity: 1.0;
      }
      
      /* Add a small custom message before the footer */
      #footer::before {
          content: "Powered by Forgejo | Hosted by My University Dept | ";
          display: inline; /* Or block if you want it on a new line */
          font-size: 0.9em;
          color: #777;
          margin-right: 5px;
      }
      
    • Save and close the file.
  3. Restart Forgejo (Optional but recommended): While CSS might be picked up dynamically, restarting ensures Forgejo recognizes the new custom file structure if it wasn't there before.

    docker compose restart forgejo
    

  4. Verify Changes:

    • Open your Forgejo instance in your browser (https://git.yourdomain.com).
    • Perform a hard refresh (e.g., Ctrl+Shift+R or Cmd+Shift+R) to bypass browser cache.
    • The top header bar should now have a green gradient background with white text.
    • Scroll down to the bottom of the page. You should see the custom text "Powered by Forgejo | Hosted by My University Dept | " displayed just before the standard footer links ("API", "Website", version number, etc.).

Conclusion: You have successfully customized the appearance of your Forgejo instance using a simple CSS override file placed in the custom directory. This demonstrates how you can easily apply visual themes or branding without modifying Forgejo's core code. For more complex changes, you would explore overriding templates.

Security Hardening Best Practices

While setting up HTTPS and using a reverse proxy significantly improves security, several other measures should be considered for a production-ready Forgejo instance.

  1. Regular Updates: Keep Forgejo, your underlying OS, Docker, Nginx, and PostgreSQL updated. Security vulnerabilities are discovered regularly, and updates often contain patches. Subscribe to Forgejo release notifications.

    • Forgejo Update (Docker): Update the image tag in docker-compose.yml (e.g., codeberg.org/forgejo/forgejo:7.1), then run docker compose pull forgejo followed by docker compose up -d forgejo. Read release notes for potential breaking changes or migration steps.
    • System Updates: sudo apt update && sudo apt upgrade -y (Debian/Ubuntu).
  2. Firewall Configuration: Ensure your server's firewall (e.g., ufw, firewalld) only allows traffic on necessary ports (e.g., 22 for SSH management, 80/443 for Nginx, 2222 for Forgejo SSH). Deny all other incoming traffic by default.

  3. Fail2Ban: Install and configure Fail2Ban to monitor logs and automatically block IP addresses that show malicious behavior (e.g., multiple failed login attempts, probing for vulnerabilities).

    • Monitor Nginx access/error logs for failed logins or suspicious requests.
    • Monitor Forgejo's own logs (./forgejo/data/log/forgejo.log) for failed logins via the web UI.
    • Monitor system SSH logs (/var/log/auth.log) for failed logins to the server itself and potentially failed Git SSH attempts (though Forgejo handles SSH auth internally, failed connections might hit the SSH daemon on port 2222).
    • Create custom Fail2Ban filters and jails targeting Forgejo login failures.
  4. Strong Passwords & 2FA: Enforce strong password policies for users. Encourage or require the use of Two-Factor Authentication (TOTP supported). Admins can manage 2FA settings in the Admin Panel.

  5. SSH Security:

    • Use SSH Keys: Disable password authentication for Git over SSH if possible (relies on users using keys).
    • Restrict SSH Key Usage (Advanced): Forgejo's SSH server can restrict what commands an SSH key can execute (typically limited to forgejo-serv commands for Git operations). This is the default and generally secure.
    • Secure Host SSH: Secure the server's main SSH daemon (running on port 22 usually) by disabling root login, disabling password authentication (use keys only), and potentially changing the default port.
  6. Forgejo Configuration (app.ini): Review security-related settings:

    • [service]: DISABLE_REGISTRATION = true (highly recommended for private instances), ENABLE_CAPTCHA, REGISTER_EMAIL_CONFIRM, DEFAULT_KEEP_EMAIL_PRIVATE = true, DEFAULT_ALLOW_CREATE_ORGANIZATION = false (if needed).
    • [security]: INSTALL_LOCK = true (should be set automatically after install), SECRET_KEY (ensure it's long and random, generated during install), REVERSE_PROXY_TRUSTED_PROXIES (list your reverse proxy's IP, e.g., 127.0.0.1, if Nginx/Forgejo are on the same host, or Docker network gateway if separated). This ensures Forgejo trusts headers like X-Forwarded-For.
    • [admin]: DISABLE_REGULAR_ORG_CREATION = true.
  7. Reverse Proxy Security Headers: Ensure your Nginx configuration includes security headers like Strict-Transport-Security (HSTS), X-Frame-Options, X-Content-Type-Options, Referrer-Policy, and potentially Content-Security-Policy (CSP). Certbot's default Nginx options often include some of these. CSP requires careful configuration to avoid breaking functionality.

  8. Database Security: Use strong, unique passwords for database users. Configure PostgreSQL to only listen on necessary interfaces (e.g., localhost or the Docker network interface) if it doesn't need to be exposed further. Use SSL_MODE = require in app.ini if connecting to the database over an untrusted network.

  9. Backup Security: Encrypt your backups and store them securely in a geographically separate location. Ensure access to the backup storage is tightly controlled.

  10. Limit Admin Access: Grant administrator privileges sparingly. Use regular user accounts for daily tasks.

  11. Monitor Logs: Regularly review Forgejo logs, Nginx logs, database logs, and system logs for any unusual activity or errors.

Workshop Implementing Security Headers and SSH Key Restrictions

Objective: Add essential security headers via the Nginx reverse proxy configuration and understand how Forgejo restricts SSH key usage.

Assumptions:

  • You are using Nginx as a reverse proxy configured for HTTPS (from Workshop 2).
  • You have shell access to your server with sudo privileges.
  • You have SSH access configured for Git operations with Forgejo.

Part 1: Adding Security Headers in Nginx

  1. Edit Nginx Configuration:

    sudo nano /etc/nginx/sites-available/forgejo.conf
    

  2. Locate HTTPS Server Block: Find the server { ... } block that listens on listen 443 ssl http2;.

  3. Add/Verify Security Headers: Inside this server block (but outside any specific location block unless you want them location-specific), add or ensure the following headers are present. Certbot might have added HSTS already.

    # Add these lines within the 'server {...}' block for port 443
    
    # Instructs browsers to always connect via HTTPS for 1 year, including subdomains
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    
    # Prevents browser from MIME-sniffing the content type
    add_header X-Content-Type-Options nosniff always;
    
    # Prevents the site from being rendered within an iframe (Clickjacking protection)
    add_header X-Frame-Options DENY always; # Or SAMEORIGIN if you need framing from same origin
    
    # Enables browser's built-in XSS protection filter
    add_header X-XSS-Protection "1; mode=block" always;
    
    # Controls how much referrer info is sent (good default for privacy/security)
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    
    # Optional: Content Security Policy (CSP) - Start restrictive and test!
    # This is a basic example, real-world policies are often more complex.
    # add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; frame-ancestors 'none';" always;
    # Warning: Implementing CSP requires careful testing as it can break functionality.
    # Start without it or use a reporting-only mode first.
    

    • Explanation: Each add_header directive adds an HTTP response header. The always parameter ensures the header is added even for error responses. Read documentation for each header to understand its implications fully. HSTS (Strict-Transport-Security) is particularly important for HTTPS enforcement.
  4. Save and Close: Save the configuration file (Ctrl+X, Y, Enter).

  5. Test Nginx Configuration:

    sudo nginx -t
    

    • Ensure it reports syntax is OK.
  6. Reload Nginx: Apply the changes.

    sudo systemctl reload nginx
    

  7. Verify Headers:

    • Open your Forgejo site in your browser.
    • Open the browser's Developer Tools (usually F12).
    • Go to the "Network" tab.
    • Refresh the page (you might need to check "Disable cache").
    • Select the main HTML document request (usually the first one for your domain).
    • Inspect the "Response Headers" section. You should see the headers you added (strict-transport-security, x-content-type-options, x-frame-options, etc.).
    • Alternatively, use an online tool like securityheaders.com to scan your domain.

Part 2: Understanding SSH Key Restrictions

This part is informational – Forgejo handles this automatically, but it's good to understand how.

  1. Examine authorized_keys: When you add an SSH public key via the Forgejo UI, Forgejo doesn't typically add it directly to a standard ~/.ssh/authorized_keys file of a system user in the traditional way. Instead, Forgejo manages SSH authentication internally.
  2. Forgejo's Internal SSH Server: The SSH server listening on port 2222 (in our setup) is part of the Forgejo binary itself.
  3. Key Lookup: When you attempt an SSH connection (e.g., git clone ssh://...), the Forgejo SSH server receives the connection request. It extracts the public key offered by your Git client.
  4. Database Check: Forgejo looks up this public key in its database (the one storing users, repositories, etc.).
  5. Command Restriction: If the key is found and associated with a valid Forgejo user, Forgejo then internally restricts the commands that this SSH session is allowed to run. It essentially forces the execution of a specific internal command handler (forgejo-serv key-<key_id>) which processes Git requests (push/pull).
  6. Result: You cannot use your Git SSH key added to Forgejo to get a general shell login on the server via port 2222, even if the key is valid. The connection is strictly limited to performing Git actions authorized for that key/user within Forgejo.

Verification (Conceptual):

  • Try to SSH directly using the Git port and your Forgejo key:
    # On your local machine
    ssh git@git.yourdomain.com -p 2222
    
  • You should receive an error message similar to:
    PTY allocation request failed on channel 0
    Hi there, You've successfully authenticated, but Forgejo does not provide shell access.
    Connection to git.yourdomain.com closed.
    
  • This confirms that Forgejo accepted your key but prevented shell access, restricting it to Git operations only.

Conclusion: You have enhanced your instance's security by adding important HTTP security headers via Nginx. You also understand that Forgejo's built-in SSH server inherently restricts SSH keys added through its UI to only perform Git-related actions, preventing unauthorized shell access through the Git SSH port.

Migrating Repositories to Forgejo

Often, you'll want to move existing repositories from other platforms (like GitHub, GitLab, Bitbucket, or another Gitea/Forgejo instance) to your self-hosted Forgejo server. Forgejo offers several ways to do this.

Methods:

  1. Migration via UI (Recommended for most cases):

    • Forgejo has a built-in migration tool accessible via the UI. Click the "+" icon -> "New Migration".
    • Source: Select the platform (GitHub, GitLab, Gitea, Git) or enter a generic Git HTTPS/SSH URL.
    • Clone Address: Enter the HTTPS or SSH URL of the repository you want to migrate. For private repositories on platforms like GitHub, you'll likely need to provide an Access Token with repository read permissions.
    • Authentication: Enter username/password or access token if required by the source.
    • Migration Information: Choose the Forgejo owner (user or org), repository name (can be different from the source), visibility (public/private).
    • Migrate Items: Crucially, you can choose what to migrate besides the Git data itself:
      • Issues
      • Pull Requests
      • Milestones
      • Labels
      • Releases (including assets)
      • Wiki
    • Benefits: Easiest method, often preserves metadata like issues and PRs (support varies by source platform). Handles LFS objects automatically if configured.
    • Limitations: May not work perfectly for all platforms or complex histories. Requires the Forgejo server to be able to reach the source repository URL.
  2. Manual Push (Mirroring):

    • If you only need the Git history (code and commits) and not issues, PRs, etc., you can manually push an existing local clone to Forgejo.
    • Steps:
      1. (Forgejo UI) Create a new, empty repository on Forgejo (e.g., my-migrated-repo). Do not initialize it with any files.
      2. (Local Machine) Navigate to your existing local clone of the source repository.
      3. (Local Machine) Add your Forgejo instance as a new remote:
        # Use SSH URL (recommended)
        git remote add forgejo ssh://git@git.yourdomain.com:2222/YourUsername/my-migrated-repo.git
        # Or HTTPS URL
        # git remote add forgejo https://git.yourdomain.com/YourUsername/my-migrated-repo.git
        
      4. (Local Machine) Push all branches and tags to the new remote:
        # Push all branches
        git push --all forgejo
        # Push all tags
        git push --tags forgejo
        
    • Benefits: Simple, reliable for Git data, doesn't require the Forgejo server to access the source.
    • Limitations: Does not migrate issues, PRs, releases, wiki, etc.
  3. Repository Mirroring:

    • Forgejo can automatically mirror repositories from other sources. This keeps your Forgejo copy updated with changes from the original source (pull mirror) or pushes changes from Forgejo back to the original source (push mirror).
    • Set up via Repository Settings -> Mirror Settings.
    • Use Case: Useful if you want a local copy of an external repository or need to gradually transition while keeping two locations in sync for a period. Can also be used as a one-time import mechanism (create a pull mirror, wait for sync, then disable mirroring).
    • Benefits: Keeps repositories synchronized. Can migrate LFS objects.
    • Limitations: Can be slightly more complex to set up authentication tokens. Primarily for keeping things in sync, not just a one-time move (though can be used for that).

Considerations:

  • Issues/PRs: The UI migration is generally the only way to preserve these. Manual pushes lose this metadata.
  • Large Files (LFS): Ensure LFS is configured correctly in your Forgejo app.ini before migrating repositories that use LFS. UI migration and mirroring usually handle LFS, but manual pushes require ensuring your local Git LFS client can push to the Forgejo remote.
  • Authentication: Migrating private repositories often requires Personal Access Tokens (PATs) from the source platform (GitHub, GitLab) with appropriate read permissions.
  • User Mapping: Migrated issues/PRs will often show the user who performed the migration as the author unless Forgejo can map usernames/emails from the source to existing Forgejo users.
  • Testing: For critical repositories, consider doing a test migration first to ensure metadata and history are transferred as expected.

Workshop Migrating a Repository from GitHub

Objective: Use Forgejo's UI migration feature to import a public repository (e.g., Forgejo's own awesome-forgejo list) from GitHub into your Forgejo instance, including issues and releases.

Assumptions:

  • Your Forgejo instance is running and accessible.
  • You are logged in to Forgejo.
  • Your Forgejo server can make outbound HTTPS connections to github.com.

Steps:

  1. Find a Repository to Migrate: We'll use the awesome-forgejo repository as an example. Its URL is https://github.com/forgejo/awesome-forgejo.git.

  2. Start Migration in Forgejo:

    • Click the "+" icon in the top right corner of the Forgejo UI.
    • Select "New Migration".
  3. Select Source:

    • Choose "GitHub" from the source options. Alternatively, you could select "Git" and paste the URL. Choosing "GitHub" might enable more specific metadata fetching.
  4. Enter Clone Address:

    • In the "Clone Address" field, paste the HTTPS URL: https://github.com/forgejo/awesome-forgejo.git
  5. Authentication (Not Needed for Public Repo):

    • Since this is a public repository, you can leave the "Auth Username / Email" and "Auth Password / Token" fields blank. If migrating a private repo, you would enter your GitHub username and a Personal Access Token here.
  6. Configure Migration:

    • Repository Owner: Select your Forgejo username or an organization you own.
    • Repository Name: Enter a name for the repository on your instance (e.g., imported-awesome-forgejo).
    • Visibility: Choose "Public" or "Private" for the migrated repository on your instance.
    • Migration Items: Ensure the following are checked (if you want them):
      • Git Data (Required)
      • Issues
      • Pull Requests
      • Milestones
      • Labels
      • Releases
      • Wiki (This repo likely doesn't have one, but good practice to check if needed)
    • Mirror Repository: Leave this unchecked for a one-time migration.
  7. Start Migration:

    • Click the "Migrate Repository" button.
  8. Monitor Progress:

    • Forgejo will start the migration process in the background. You might be redirected to the new repository page, which may initially be empty or show a "Migration in progress" message.
    • The time taken depends on the size of the repository and the amount of metadata. awesome-forgejo is small and should migrate quickly.
    • You can check the status in the Site Administration -> System Monitor -> Background Tasks queue if needed, but usually just waiting and refreshing the repository page is sufficient.
  9. Verify Migrated Repository:

    • Once the migration is complete (the repository page loads normally), verify the content:
      • Code: Check if the files and commit history match the original GitHub repository.
      • Issues: Go to the "Issues" tab. You should see the issues imported from GitHub. Note that authors might be mapped to your user if the original authors don't exist on your Forgejo instance.
      • Pull Requests: Check the "Pull Requests" tab (look at closed PRs).
      • Releases: Check the "Releases" tab.

Conclusion: You have successfully migrated a public repository from GitHub to your self-hosted Forgejo instance using the built-in UI migration tool. You observed that not only the Git history but also associated metadata like issues and releases were transferred, providing a much richer import than a simple manual Git push. This method is highly effective for bringing external projects into your controlled environment.

Conclusion

Congratulations on completing this comprehensive guide to self-hosting Forgejo! You have journeyed from the fundamental concepts of why self-hosting a Git server is beneficial, through the basic installation and configuration using Docker, securing your instance with Nginx and HTTPS, leveraging a robust PostgreSQL database, managing users and permissions effectively, and implementing crucial backup strategies.

Furthermore, you explored advanced capabilities including setting up a CI/CD pipeline with Forgejo Actions, customizing the look and feel of your instance, applying security hardening techniques, and migrating existing repositories from other platforms.

By mastering these steps, you have empowered yourself with:

  • Full Control: Your code, your data, your rules, running on your infrastructure.
  • Enhanced Privacy & Security: Reduced reliance on third-party services and the ability to implement tailored security measures.
  • Customization: The power to adapt Forgejo to your specific needs and workflows.
  • Valuable Skills: Practical experience in Linux administration, Docker, Nginx, databases, Git server management, and CI/CD principles.

Self-hosting Forgejo is not just about running a Git server; it's about building a resilient, private, and efficient platform for your software development endeavors, whether for personal projects, university assignments, or organizational collaboration.

Where to Go Next?

  • Explore app.ini: Dive deeper into the extensive configuration options available in app.ini via the official Forgejo Configuration Cheat Sheet documentation.
  • Advanced Actions: Experiment with more complex Forgejo Actions workflows, including building Docker images, deploying applications, or integrating with external services.
  • Federation (ActivityPub): Investigate Forgejo's experimental support for federation using ActivityPub, allowing interaction with other federated instances.
  • Monitoring & Alerting: Set up monitoring tools (like Prometheus/Grafana) to track Forgejo's performance and health, and configure alerting for critical issues.
  • Contribute: Forgejo is community-driven. Consider contributing back by reporting bugs, suggesting features, improving documentation, or even contributing code.

Remember that the official Forgejo documentation and the community forum/chat are invaluable resources as you continue to use and potentially expand your self-hosted setup. Keep your instance updated, maintain your backups diligently, and enjoy the freedom and control that comes with running your own Git service.