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


Self hosting git server Gitea

Introduction

Welcome to this comprehensive guide on self-hosting Gitea, a lightweight and powerful Git service, on a Linux environment. In the world of software development, version control systems (VCS) are fundamental tools, and Git has emerged as the de facto standard. While popular hosted platforms like GitHub, GitLab, and Bitbucket offer convenient solutions, self-hosting your Git service provides unparalleled control, privacy, customization, and a valuable learning experience.

What is Git?

Before diving into Gitea, let's briefly touch upon Git. Git is a distributed version control system designed to handle everything from small to very large projects with speed and efficiency. It allows multiple developers to work on the same codebase simultaneously, tracking changes, managing branches, merging contributions, and reverting to previous states when necessary. Its distributed nature means every developer has a full copy of the repository history, enabling offline work and enhancing redundancy. Understanding Git fundamentals is crucial before managing a Git server.

What is Gitea?

Gitea is a community-managed, open-source forge software package for hosting software development version control using Git. It is incredibly lightweight, written in Go, and aims to be the easiest, fastest, and most painless way of setting up a self-hosted Git service. Key features include:

  • Lightweight: Requires minimal CPU and RAM resources compared to heavier alternatives like GitLab.
  • Cross-platform: Runs on Linux, macOS, Windows, and ARM architectures.
  • Easy Installation: Offers multiple installation methods, including binaries, Docker, and third-party packages.
  • Feature-Rich: Provides repository hosting, code review (pull requests), issue tracking, wikis, user/organization management, webhooks, package registries, and basic CI/CD (Gitea Actions).
  • User-Friendly Interface: Offers a clean and intuitive web UI similar to popular hosted platforms.
  • Extensible: Supports various database backends (SQLite3, PostgreSQL, MySQL/MariaDB, MSSQL) and authentication methods (LDAP, OAuth2).

Why Self-Host Gitea?

While hosted platforms are convenient, self-hosting Gitea offers distinct advantages, particularly for students, small teams, or organizations with specific needs:

  • Complete Control: You manage the server, the data, the updates, and the configuration. You decide when and how to upgrade or modify the system.
  • Enhanced Privacy & Security: Your code and data reside on your own infrastructure, reducing reliance on third-party providers and allowing you to implement custom security measures.
  • Cost-Effective: Gitea itself is free and open-source. Your only costs are related to the server infrastructure (hardware, hosting, bandwidth).
  • Customization: Modify Gitea's appearance, integrate it with internal tools via webhooks or APIs, and fine-tune performance settings.
  • Learning Opportunity: Setting up and managing a service like Gitea provides invaluable hands-on experience with Linux administration, databases, networking, security, and web server configuration.
  • No User Limits (Software): Gitea doesn't impose artificial limits on the number of users or repositories (though your hardware resources will be the practical limit).

Structure of this Guide

This guide is structured progressively to take you from basic setup to advanced administration:

  1. Basic Setup and Configuration: Covers prerequisites, installation using the binary method, initial web configuration, and fundamental Gitea concepts.
  2. Intermediate Configuration and Usage: Focuses on securing your instance with HTTPS, managing users/authentication, using webhooks, exploring core features like issues and pull requests, and migrating repositories.
  3. Advanced Administration and Customization: Delves into backup/restore procedures, performance tuning, high availability concepts, UI customization, and advanced configuration file management.
  4. Troubleshooting Common Issues: Provides guidance on diagnosing and resolving typical problems encountered during setup and operation.
  5. CI/CD Integration with External Tools: Explains how to connect Gitea to external CI/CD systems like Jenkins or Drone CI using webhooks.
  6. Gitea Actions (Built-in CI/CD): Covers setting up and using Gitea's integrated CI/CD solution.
  7. Using the Gitea API: Demonstrates interacting with Gitea programmatically via its REST API.
  8. Advanced Configuration Deep Dive (app.ini): Explores less common but powerful configuration options.
  9. Containerization with Docker: Guides you through running Gitea using Docker and Docker Compose.
  10. Security Hardening: Provides actionable steps to further enhance the security of your Gitea instance.
  11. Community and Contribution: Discusses how to engage with the Gitea community and contribute back to the project.

Each major section includes hands-on "Workshop" subsections with step-by-step instructions to apply the concepts you've learned in a practical, real-world scenario. Let's begin the journey of setting up your personal Git powerhouse!

1. Basic Setup and Configuration

This section covers the essential steps to get a functional Gitea instance running on your Linux server. We'll start with understanding the core components and prerequisites, then proceed with a straightforward installation method using the pre-compiled binary, and finally perform the initial setup through the web interface.

Understanding Gitea's Core Concepts

Before installing, let's familiarize ourselves with some key terms and concepts within Gitea:

  • Repositories: The fundamental unit in Git and Gitea. A repository (or "repo") contains all your project's files, revision history, branches, and tags. Gitea provides a web interface to view, manage, and interact with these repositories.
  • Users: Individual accounts that can own repositories, contribute to others' repositories, open issues, create pull requests, and participate in organizations. Each user has a unique username and profile.
  • Organizations: A way to group users and repositories together, typically representing a team, company, or project group. Organizations allow for centralized management of permissions across multiple repositories and members.
  • Teams: Within an organization, you can create teams (e.g., "developers," "designers," "testers") to manage access permissions to specific repositories more granularly. Assigning repositories to teams grants access to all team members according to the defined permission level (Read, Write, Admin).
  • Basic Workflow: The common Git operations like git clone (copying a remote repository locally), git pull (fetching changes from the remote), git add (staging changes), git commit (saving changes to local history), and git push (sending local commits to the remote Gitea server) are fully supported. Gitea primarily acts as the central "remote" repository server.
  • Gitea Architecture: At its core, Gitea is a single executable binary written in Go. It interacts with:
    • Git: The underlying version control system command-line tools must be installed on the server.
    • Database: Stores metadata like user accounts, repository information (but not the Git data itself), issues, pull requests, comments, permissions, etc. (Options: SQLite3, PostgreSQL, MySQL/MariaDB, TiDB, MSSQL).
    • Configuration File (app.ini): Contains all settings for Gitea, such as database connection details, server ports, repository paths, security options, etc.
    • Data Directories: Specific folders on the server where Gitea stores repository Git data, attachments, user avatars, session information, LFS objects, etc.
    • Web Server: Gitea includes a built-in web server to handle HTTP/HTTPS requests for the web UI and Git operations over HTTP(S). It can also run behind a reverse proxy like Nginx or Apache.

Prerequisites for Installation

To ensure a smooth installation process, you need to prepare your Linux server environment.

  • Linux Server: A server running a modern Linux distribution. This guide will primarily use commands compatible with Debian/Ubuntu, but equivalents for CentOS/RHEL/Fedora will be mentioned where applicable. A minimal server installation is usually sufficient. You'll need SSH access and sudo privileges.
    • Examples: Ubuntu 20.04/22.04 LTS, Debian 11/12, CentOS Stream 8/9, RHEL 8/9.
  • Dedicated User Account: It is highly recommended not to run Gitea as the root user for security reasons. We will create a dedicated system user (commonly named git) to run the Gitea process and own its files.
    • Security Principle: Principle of Least Privilege. The git user should only have the permissions necessary to run Gitea and access its data.
  • Database: Gitea requires a database to store its metadata. Choose one:
    • SQLite3: Simplest option, stores the database in a single file. Suitable for small, single-server setups or testing. Performance might degrade under heavy load. Requires no separate database server installation.
    • PostgreSQL: A powerful, open-source object-relational database system. Recommended for larger installations requiring robustness and scalability. Requires installing and configuring a PostgreSQL server.
    • MySQL/MariaDB: Widely used open-source relational database systems. Also recommended for production environments. Requires installing and configuring a MySQL or MariaDB server.
    • For this basic setup, we will initially focus on SQLite3 for simplicity.
  • Git Installation: Gitea relies on the standard Git command-line tools being installed on the server. Gitea calls these tools in the background to perform Git operations. Ensure you have a recent version (check Gitea documentation for minimum required version, usually 2.x or higher).
  • Basic Networking Concepts:
    • Ports: Gitea's built-in web server listens on specific network ports. The default HTTP port is 3000, and the default SSH port for Git operations is 22 (Gitea uses the system's SSH server by default, but can be configured with a built-in SSH server on a different port like 2222).
    • Firewall: Your server's firewall must be configured to allow incoming traffic on the ports Gitea uses (e.g., port 3000 for HTTP, port 22 for SSH, and later potentially port 443 for HTTPS). Common firewall tools are ufw (Uncomplicated Firewall, common on Ubuntu/Debian) and firewalld (common on CentOS/RHEL/Fedora).

Workshop Preparing the Server

This workshop guides you through setting up the basic server environment needed before installing Gitea itself. Execute these commands on your Linux server via SSH.

1. Update System Packages: Ensure your system's package list and installed packages are up-to-date.

  • Debian/Ubuntu:
    sudo apt update
    sudo apt upgrade -y
    
  • CentOS/RHEL/Fedora:
    sudo dnf update -y
    # Or for older CentOS/RHEL versions:
    # sudo yum update -y
    
    Explanation: apt update (or dnf update) refreshes the list of available packages from the repositories. apt upgrade (or dnf upgrade) installs the latest versions of all packages currently installed on the system. The -y flag automatically answers "yes" to prompts.

2. Create Dedicated git User and Group: Create a system user named git that will run the Gitea service.

sudo adduser --system --shell /bin/bash --group --disabled-password --home /home/git git

Explanation:

  • sudo adduser: Command to add a new user.
  • --system: Creates a system user, typically with a UID below 1000 and without certain login features, suitable for running services.
  • --shell /bin/bash: Sets the user's login shell to bash (needed for SSH access if using the system SSH server). You could use /bin/false or /usr/sbin/nologin if you only plan to use Gitea's built-in SSH server or HTTP(S) access, but /bin/bash is often safer for initial setup and troubleshooting via system SSH.
  • --group: Creates a group with the same name as the user (git).
  • --disabled-password: Ensures the user cannot log in using a password (recommended for service accounts; access will be via SSH keys or sudo su).
  • --home /home/git: Specifies the home directory for the git user.
  • git: The username for the new user.

3. Install Git: Install the Git command-line tools.

  • Debian/Ubuntu:
    sudo apt update
    sudo apt install -y git
    
  • CentOS/RHEL/Fedora:
    sudo dnf install -y git
    
    Explanation: This uses the system's package manager to download and install the git package.
  • Verify Installation:
    git --version
    
    This should output the installed Git version (e.g., git version 2.34.1).

4. Install Database (SQLite3 - Optional for this step): SQLite3 support is often built into the Go standard library used by Gitea, or the Gitea binary itself might include it. However, installing the sqlite3 command-line tool can be useful for debugging or manually inspecting the database file.

  • Debian/Ubuntu:
    sudo apt update
    sudo apt install -y sqlite3
    
  • CentOS/RHEL/Fedora:
    sudo dnf install -y sqlite
    
    Explanation: Installs the command-line utility to interact with SQLite3 databases. Gitea itself usually doesn't require this package to use SQLite3, but it's helpful for administration.

5. Configure Basic Firewall Rules: Allow traffic on the default Gitea HTTP port (3000) and the standard SSH port (22).

  • Using ufw (Common on Ubuntu/Debian):
    sudo ufw allow ssh # Allows traffic on port 22 (defined in /etc/services)
    sudo ufw allow 3000/tcp # Allows TCP traffic on port 3000
    sudo ufw enable # Enable the firewall if not already active
    sudo ufw status # Verify the rules
    
  • Using firewalld (Common on CentOS/RHEL/Fedora):
    sudo firewall-cmd --permanent --add-service=ssh # Allows traffic for the ssh service
    sudo firewall-cmd --permanent --add-port=3000/tcp # Allows TCP traffic on port 3000
    sudo firewall-cmd --reload # Apply the permanent rules
    sudo firewall-cmd --list-all # Verify the rules
    
    Explanation: These commands add rules to the firewall to permit incoming connections on the specified ports. ufw enable or firewall-cmd --reload activate the changes. Always ensure SSH access is allowed before enabling a firewall to avoid locking yourself out.

Your server is now prepared with the necessary user, software (Git), and basic network configuration to proceed with the Gitea installation.

Installation Method Binary

Installing Gitea using the official pre-compiled binary is one of the most straightforward methods. It doesn't rely on specific package managers and gives you direct control over the executable and its location.

1. Download the Latest Gitea Binary:

  • Go to the Gitea releases page: https://dl.gitea.com/gitea/ (or https://github.com/go-gitea/gitea/releases)
  • Find the latest stable release version (e.g., 1.21.5).
  • Locate the binary suitable for your server's architecture (usually linux-amd64). Right-click the link and copy the URL.
  • Use wget or curl on your server to download it. Replace 1.21.5 with the actual latest version number.

    # Example using wget for version 1.21.5 on amd64:
    wget https://dl.gitea.com/gitea/1.21.5/gitea-1.21.5-linux-amd64
    

2. Make the Binary Executable and Move it:

  • Make the downloaded file executable.
    chmod +x gitea-1.21.5-linux-amd64
    
  • Move the binary to a standard location for executables, like /usr/local/bin, and rename it to gitea for easier use.
    sudo mv gitea-1.21.5-linux-amd64 /usr/local/bin/gitea
    
  • Verify it runs:
    gitea --version
    
    This should output the Gitea version and the compatible Git version.

3. Create Necessary Directories:

Gitea needs specific directories to store its configuration, data, and logs. We'll follow the Linux Filesystem Hierarchy Standard (FHS) recommendations.

  • Configuration: /etc/gitea
  • Data Root: /var/lib/gitea (will contain repositories, attachments, etc.)
  • Logs: /var/log/gitea

sudo mkdir -p /etc/gitea /var/lib/gitea /var/log/gitea
Explanation: mkdir -p creates the directories and any necessary parent directories without error if they already exist.

4. Set Permissions:

The git user we created earlier needs to own and have write access to these directories.

sudo chown git:git /etc/gitea /var/lib/gitea /var/log/gitea
sudo chmod 750 /etc/gitea # Owner rwx, Group rx, Others ---
sudo chmod 770 /var/lib/gitea # Owner rwx, Group rwx, Others --- (Group write needed for some operations)
sudo chmod 770 /var/log/gitea # Owner rwx, Group rwx, Others --- (Group write potentially needed by log rotation)

Explanation:

  • chown git:git: Changes the owner and group of the directories to git.
  • chmod: Sets the access permissions.
  • 750 for /etc/gitea: git user can read/write/execute (enter), git group can read/execute, others have no access. Configuration should be protected.
  • 770 for /var/lib/gitea and /var/log/gitea: Both git user and git group can read/write/execute. This allows flexibility, for example, if helper scripts or web server processes running as part of the git group need access. Others have no access.

5. Create a systemd Service File:

To manage the Gitea process (start, stop, restart, enable on boot), we'll create a systemd service file. systemd is the standard init system for most modern Linux distributions.

Create the file /etc/systemd/system/gitea.service using a text editor (like nano or vim) with sudo:

sudo nano /etc/systemd/system/gitea.service

Paste the following content into the file. Adjust paths if you chose different locations.

[Unit]
Description=Gitea (Git with a cup of tea)
After=syslog.target
After=network.target
# Uncomment/adjust the following lines if you are using MySQL/MariaDB or PostgreSQL
# Wants=mysql.service
# Wants=postgresql.service

[Service]
# Modify these two values and uncomment them if you have
# potentially chosen a different user/group
User=git
Group=git
Type=simple
WorkingDirectory=/var/lib/gitea/
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea

# If you want to bind Gitea to a lower port number (< 1024) uncomment
# the two values below. You need Linux Kernel 3.9+ for this.
# CapabilityBoundingSet=CAP_NET_BIND_SERVICE
# AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

Explanation:

  • [Unit]: Defines metadata and dependencies. After= specifies services that should be started before Gitea. Wants= specifies optional dependencies.
  • [Service]: Configures the service execution.
    • User=git, Group=git: Specifies the user/group to run the process as (matches the user we created). Explicitly setting User/Group is best practice.
    • Type=simple: Assumes the ExecStart command is the main process.
    • WorkingDirectory: Sets the working directory for the Gitea process. This is important for relative paths.
    • ExecStart: The command to start Gitea. It runs the gitea binary with the web subcommand and specifies the configuration file location.
    • Restart=always: Automatically restarts Gitea if it crashes.
    • Environment: Sets environment variables for the Gitea process. GITEA_WORK_DIR is particularly important.
    • CapabilityBoundingSet, AmbientCapabilities: Used only if you need Gitea to bind to privileged ports (like 80 or 443) directly, which is generally not recommended (use a reverse proxy instead).
  • [Install]: Defines how the service integrates with system boot targets. WantedBy=multi-user.target ensures Gitea starts on normal system boot.

6. Enable and Start the Gitea Service:

Tell systemd to recognize the new service file, enable it to start on boot, and start it now.

sudo systemctl daemon-reload # Reload systemd manager configuration
sudo systemctl enable gitea # Enable Gitea to start on boot
sudo systemctl start gitea # Start Gitea immediately

7. Check Service Status:

Verify that the service started correctly.

sudo systemctl status gitea
Look for output indicating "active (running)". Check the logs for any errors if it failed:

sudo journalctl -u gitea -f
(Press Ctrl+C to exit the log view).

Workshop Installing Gitea via Binary

Let's perform the binary installation step-by-step, assuming you have completed the "Workshop Preparing the Server".

1. Download the Binary: (Remember to check the Gitea releases page for the absolute latest version number and correct architecture)

# Go to your preferred temporary directory (optional)
cd /tmp

# Fetch the binary (Replace version 1.21.5 and architecture if needed)
wget https://dl.gitea.com/gitea/1.21.5/gitea-1.21.5-linux-amd64

# Check download integrity (Optional but recommended)
# Find the SHA256 checksum on the Gitea releases page and verify:
# sha256sum gitea-1.21.5-linux-amd64
# Compare the output with the checksum listed on the Gitea website.

2. Install the Binary:

# Make it executable
chmod +x gitea-1.21.5-linux-amd64

# Move it to /usr/local/bin and rename
sudo mv gitea-1.21.5-linux-amd64 /usr/local/bin/gitea

# Verify
gitea --version

3. Create Directories and Set Permissions:

# Create directories
sudo mkdir -p /etc/gitea /var/lib/gitea /var/log/gitea

# Set ownership
sudo chown git:git /etc/gitea /var/lib/gitea /var/log/gitea

# Set permissions
sudo chmod 750 /etc/gitea
sudo chmod 770 /var/lib/gitea
sudo chmod 770 /var/log/gitea

# Verify permissions (optional)
# ls -ld /etc/gitea /var/lib/gitea /var/log/gitea

4. Create the gitea.service File:

# Use nano (or vim/your preferred editor) to create the file
sudo nano /etc/systemd/system/gitea.service

# Paste the following content:
# --- PASTE START ---
[Unit]
Description=Gitea (Git with a cup of tea)
After=syslog.target
After=network.target
# Uncomment/adjust the following lines if you plan to use MySQL/MariaDB or PostgreSQL later
# Wants=mysql.service
# Wants=postgresql.service

[Service]
User=git
Group=git
Type=simple
WorkingDirectory=/var/lib/gitea/
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea

# Keep these commented out unless binding to ports < 1024 directly
# CapabilityBoundingSet=CAP_NET_BIND_SERVICE
# AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target
# --- PASTE END ---

# Save the file and exit the editor (Ctrl+X, then Y, then Enter in nano)

5. Enable and Start the Gitea Service:

sudo systemctl daemon-reload
sudo systemctl enable gitea
sudo systemctl start gitea

6. Verify Gitea is Running:

sudo systemctl status gitea
  • Look for Active: active (running) in green.
  • Note the Main PID (Process ID).
  • Check the recent log lines shown by systemctl status. They should indicate Gitea is listening, often on 0.0.0.0:3000.
# Check the logs directly if needed
sudo journalctl -u gitea -n 50 --no-pager # Show last 50 lines without paging
# Or follow logs in real-time:
# sudo journalctl -u gitea -f

If the service is running, you are ready for the initial web configuration!

Initial Gitea Web Configuration

With the Gitea service running, the final step in the basic setup is configuring it through its web interface. Gitea provides a convenient setup page for first-time installations.

1. Accessing the Web Installer:

  • Open your web browser and navigate to http://your_server_ip:3000. Replace your_server_ip with the actual IP address or domain name of your Linux server.
  • You should see the Gitea "Initial Configuration" page. If the page doesn't load, double-check:
    • Is the gitea service running (systemctl status gitea)?
    • Are there any errors in the Gitea logs (journalctl -u gitea)?
    • Is the firewall configured correctly to allow port 3000 (sudo ufw status or sudo firewall-cmd --list-all)?
    • Is there any network issue preventing access to your server?

2. Database Settings:

  • Database Type: Select the database you prepared. For this basic setup, choose SQLite3. If you installed PostgreSQL or MySQL/MariaDB, select the appropriate type.
  • Path (for SQLite3): Gitea suggests a path for the SQLite database file. The recommended location is within the Gitea data directory. Use /var/lib/gitea/data/gitea.db. Make sure the /var/lib/gitea/data directory will be created with correct permissions by Gitea (it usually handles this). Note: If you chose PostgreSQL or MySQL/MariaDB, you would instead provide the Host (e.g., 127.0.0.1:5432 or 127.0.0.1:3306), User, Password, and Database Name you configured previously.

3. General Settings:

  • Site Title: The name displayed in browser tabs and on the site header (e.g., "My Personal Git Server").
  • Repository Root Path: The absolute path on the server where Gitea will store the actual Git repository data (.git directories). The default, /var/lib/gitea/data/gitea-repositories, is appropriate given our directory structure. Ensure this path is writable by the git user. Use the default /var/lib/gitea/data/gitea-repositories.
  • LFS Root Path: Path for Git Large File Storage objects. Default data/lfs (resolving to /var/lib/gitea/data/lfs) is fine.
  • Run As Username: This should match the system user running Gitea, which is git.
  • SSH Server Domain: The domain name or IP address users will use to clone/push repositories via SSH (e.g., your_domain.com or your_server_ip).
  • SSH Server Port: The port your system's SSH server is listening on (usually 22). If you configured Gitea's built-in SSH server, enter that port (e.g., 2222). For now, assume standard system SSH on port 22.
  • Gitea HTTP Listen Port: The port Gitea's web server listens on. We configured the service to use port 3000.
  • Gitea Base URL: The full URL users will use to access the Gitea web interface (e.g., http://your_domain.com:3000 or http://your_server_ip:3000). This setting is crucial for generating correct clone URLs and links in emails/webhooks.
  • Log Path: The directory where Gitea stores its log files. The default /var/log/gitea matches our setup.

4. Optional Settings (Review Carefully):

  • Server and Third-Party Settings:
    • Disable Gravatar: Check if you don't want to use the Gravatar service for user profile pictures.
    • Enable Federated Avatars: Allows using alternative federated avatar lookup services (like Libravatar).
    • Enable OpenID Sign-In / Sign-Up: Allows users to log in using OpenID providers.
  • Email Service Settings: Configure later if you need email notifications (user registration, password resets, etc.). Leave disabled for now.
  • Administrator Account Settings: Crucially, create your admin account here. Enter a username, password, and email address for the initial administrator user. Do not skip this!
  • More Settings:
    • Disable Self-registration: Highly recommended to check this box unless you intend to run a public Gitea instance where anyone can sign up. Otherwise, only the admin can create new users.
    • Enable Captcha: Adds protection against automated sign-ups/logins if registration is enabled.
    • Require Sign-in to View Pages: Increases privacy by hiding all content from anonymous users. Check this if you want a completely private server.

5. Install Gitea:

  • Review all settings carefully.
  • Click the "Install Gitea" button at the bottom.
  • Gitea will save the configuration to /etc/gitea/app.ini, create the necessary database structure (or file for SQLite3), and redirect you to the login page or the dashboard if successful.

Configuration File: The web installer creates the main configuration file at /etc/gitea/app.ini. You can manually edit this file later (after stopping Gitea, editing, and restarting) to change settings or access more advanced options not available in the initial web setup.

Workshop First Time Gitea Setup

Let's walk through the web configuration process. Make sure the gitea service is running and you can access http://your_server_ip:3000 in your browser.

1. Access the Web UI: Open http://your_server_ip:3000 in your browser. You should see the "Initial Configuration" page.

2. Configure Database:

  • Database Type: Select SQLite3.
  • Path: Enter /var/lib/gitea/data/gitea.db.

3. Configure General Settings:

  • Site Title: Enter something descriptive, like My Gitea Server.
  • Repository Root Path: Keep the default /var/lib/gitea/data/gitea-repositories.
  • Run As Username: Enter git.
  • SSH Server Domain: Enter your server's IP address or domain name (e.g., 192.168.1.100 or gitea.example.com).
  • SSH Server Port: Enter 22 (unless you specifically configured SSH differently).
  • Gitea HTTP Listen Port: Keep 3000.
  • Gitea Base URL: Enter http://your_server_ip:3000 (replace your_server_ip with the actual IP or domain).
  • Log Path: Keep the default /var/log/gitea.

4. Configure Optional Settings:

  • Administrator Account:
    • Enter a Username (e.g., admin).
    • Enter a strong Password.
    • Confirm the password.
    • Enter your Email address.
  • Important: Check the box for Disable Self-registration.
  • Optional: Check Require Sign-in to View Pages if desired.

5. Install Gitea:

  • Double-check all settings.
  • Click the "Install Gitea" button.

6. Log In:

  • You should be redirected to the login page.
  • Log in using the administrator username and password you just created.

7. Explore the Dashboard:

  • You are now logged into your Gitea instance! Explore the dashboard, user settings (top right avatar -> Settings), and site administration panel (top right avatar -> Site Administration).

8. Create Your First Repository:

  • Click the + icon in the top right corner and select "New Repository".
  • Owner: Select your admin user.
  • Repository Name: Enter my-first-repo.
  • Description: (Optional) Add a short description.
  • Keep it Public or make it Private.
  • (Optional) Check "Initialize Repository" to add a README, .gitignore, and license. Let's check this for simplicity.
  • Click "Create Repository".

9. Clone, Commit, and Push via HTTP: Now, let's test the basic Git workflow using HTTP(S) (we'll configure SSH access later).

  • On your local machine (not the server):
    • Make sure you have Git installed (git --version).
    • Open your terminal or command prompt.
    • Copy the HTTP clone URL from your Gitea repository page (it looks like http://your_server_ip:3000/admin/my-first-repo.git).
    • Clone the repository:
      git clone http://your_server_ip:3000/admin/my-first-repo.git
      # Replace 'admin' if you used a different admin username
      # Replace your_server_ip with the correct IP/domain
      
    • You will be prompted for your Gitea username (admin) and password.
    • Navigate into the new directory:
      cd my-first-repo
      
    • Create a new file:
      echo "Hello Gitea!" > hello.txt
      
    • Add the file to Git's staging area:
      git add hello.txt
      
    • Commit the change locally:
      git commit -m "Add hello.txt"
      
    • Push the change back to your Gitea server:
      git push origin main # Or 'master' if that's your default branch name
      
    • Enter your Gitea username and password again when prompted.
  • Verify: Refresh the repository page in your Gitea web UI. You should see the hello.txt file and the new commit message.

Congratulations! You have successfully installed, configured, and tested a basic Gitea instance.

2. Intermediate Configuration and Usage

With a basic Gitea instance running, it's time to enhance its functionality, security, and usability. This section covers setting up HTTPS for secure connections, configuring user authentication methods like LDAP, utilizing Git hooks and webhooks for automation, and exploring Gitea's collaborative features like issue tracking and pull requests.

Securing Gitea with HTTPS

Running Gitea over plain HTTP (http://) is insecure because all data, including login credentials and code, is transmitted in clear text over the network. Enabling HTTPS (https://) encrypts this traffic, which is crucial for any production or even personal setup handling sensitive data.

Why HTTPS is Crucial:

  • Encryption: Protects login credentials, API tokens, source code, and other sensitive data from eavesdropping.
  • Authentication: Verifies that you are connecting to the legitimate Gitea server and not an imposter (man-in-the-middle attack).
  • Data Integrity: Ensures that the data transmitted between your browser/Git client and the server has not been tampered with.
  • Browser Trust: Modern browsers increasingly flag HTTP sites as "Not Secure," which can deter users. Some browser features might also be disabled on non-secure origins.
  • Requirement for Features: Some integrations or web standards (like Service Workers, certain Web APIs) require HTTPS.

Methods for Enabling HTTPS:

  1. Using a Reverse Proxy (Recommended): This is the most common and flexible approach. You set up a dedicated web server like Nginx or Apache in front of Gitea.

    • The reverse proxy handles incoming HTTPS connections (port 443).
    • It manages the SSL/TLS certificates.
    • It decrypts the HTTPS traffic.
    • It forwards the requests to the Gitea instance running on its local HTTP port (e.g., localhost:3000).
    • Gitea itself continues to run on HTTP locally, simplifying its configuration.
    • Proxies offer additional benefits like load balancing, caching, request filtering, and serving static files more efficiently.
    • Tools like Let's Encrypt provide free, trusted SSL certificates that can be easily integrated with Nginx or Apache using tools like Certbot.
  2. Let's Encrypt Integration with Reverse Proxy: Certbot is a tool that automates the process of obtaining and renewing SSL certificates from Let's Encrypt and configuring your web server (Nginx, Apache) to use them. This is the preferred way to get free and auto-renewing certificates.

  3. Gitea's Built-in HTTPS: Gitea can directly handle HTTPS connections. You would need to:

    • Obtain an SSL certificate and private key (either from a Certificate Authority like Let's Encrypt or by creating a self-signed certificate).
    • Configure the paths to the certificate (CERT_FILE) and key (KEY_FILE) in Gitea's app.ini under the [server] section.
    • Change the PROTOCOL setting in app.ini to https.
    • Change the HTTP_PORT to 443 (or another port) and update firewall rules.
    • Drawbacks: This method is less flexible than using a reverse proxy. Managing certificates directly within Gitea can be more cumbersome, especially renewal. Running Gitea directly on port 443 might require special permissions (CAP_NET_BIND_SERVICE or running as root, which is discouraged). Self-signed certificates generate browser warnings and are unsuitable for public access or easy Git client configuration.

We will focus on the recommended method: Nginx as a reverse proxy with Let's Encrypt.

Workshop Setting up Nginx Reverse Proxy with Let's Encrypt

This workshop assumes:

  • You have completed the basic Gitea setup. Gitea is running on http://localhost:3000 (accessible only from the server itself after this setup).
  • You have a registered domain name (e.g., gitea.yourdomain.com) pointing to your server's public IP address via DNS A or AAAA records.
  • Ports 80 (for HTTP challenge) and 443 (for HTTPS) are open in your server's firewall and forwarded if necessary (update ufw or firewalld).

1. Update Firewall Rules: Allow HTTP (port 80) and HTTPS (port 443) traffic. Port 80 is needed by Certbot for the Let's Encrypt validation process (HTTP-01 challenge).

  • Using ufw:
    sudo ufw allow http # Allows port 80
    sudo ufw allow https # Allows port 443
    sudo ufw reload
    sudo ufw status # Verify 80/tcp and 443/tcp are allowed
    
  • Using firewalld:
    sudo firewall-cmd --permanent --add-service=http
    sudo firewall-cmd --permanent --add-service=https
    sudo firewall-cmd --reload
    sudo firewall-cmd --list-all # Verify http and https services are added
    

2. Install Nginx:

  • Debian/Ubuntu:
    sudo apt update
    sudo apt install -y nginx
    
  • CentOS/RHEL/Fedora:
    sudo dnf install -y nginx
    
  • Start and Enable Nginx:
    sudo systemctl start nginx
    sudo systemctl enable nginx
    sudo systemctl status nginx # Verify it's active (running)
    
    You should now be able to access your server's IP address in a browser and see the default Nginx welcome page.

3. Install Certbot: The recommended way to install Certbot is using snapd.

# Install snapd if not present (usually is on Ubuntu)
sudo apt update # Or sudo dnf update
sudo apt install snapd # Or sudo dnf install snapd
sudo systemctl enable --now snapd.socket # For RHEL/CentOS/Fedora if needed

# Ensure snapd is up-to-date
sudo snap install core; sudo snap refresh core

# Remove any old OS packages for certbot to avoid conflicts
sudo apt remove certbot # Or sudo dnf remove certbot

# Install Certbot using snap
sudo snap install --classic certbot

# Prepare the Certbot command for use
sudo ln -s /snap/bin/certbot /usr/bin/certbot

4. Configure Nginx for Gitea: Create an Nginx server block configuration file for your Gitea domain.

# Replace 'gitea.yourdomain.com' with your actual domain
sudo nano /etc/nginx/sites-available/gitea.yourdomain.com.conf

Paste the following configuration, replacing gitea.yourdomain.com with your domain:

server {
    listen 80;
    server_name gitea.yourdomain.com;

    # Redirect HTTP to HTTPS (Certbot will likely manage this later, but good practice)
    location / {
        return 301 https://$host$request_uri;
    }

    # Location for Let's Encrypt challenges
    location /.well-known/acme-challenge/ {
        root /var/www/html; # Or another suitable path Nginx can write to
        allow all;
    }
}

server {
    listen 443 ssl http2; # Enable SSL and HTTP/2
    server_name gitea.yourdomain.com;

    # SSL certificate paths (Certbot will configure these)
    # ssl_certificate /etc/letsencrypt/live/gitea.yourdomain.com/fullchain.pem;
    # ssl_certificate_key /etc/letsencrypt/live/gitea.yourdomain.com/privkey.pem;
    # include /etc/letsencrypt/options-ssl-nginx.conf; # Recommended SSL parameters
    # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # Diffie-Hellman parameters

    # Increase client body size for large pushes/uploads
    client_max_body_size 100m; # Adjust as needed (e.g., 500m, 1g)

    location / {
        proxy_pass http://localhost:3000; # Forward requests to Gitea
        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; # Important: tells Gitea it's behind HTTPS
        proxy_set_header Upgrade $http_upgrade; # For WebSocket support (live updates in UI)
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 900s; # Optional: Increase timeout for long Git operations
        proxy_connect_timeout 900s;
        proxy_send_timeout 900s;
    }

    # Optional: Add security headers (uncomment and potentially customize)
    # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    # add_header X-Content-Type-Options nosniff always;
    # add_header X-Frame-Options SAMEORIGIN always;
    # add_header Referrer-Policy strict-origin-when-cross-origin always;
}

# Required for WebSocket connection upgrade handling
map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

Explanation:

  • The first server block listens on port 80 and is primarily for redirecting to HTTPS and handling Let's Encrypt challenges.
  • The second server block listens on port 443 for HTTPS.
  • client_max_body_size is important for allowing large Git pushes or file uploads.
  • The location / block defines the reverse proxy setup:
    • proxy_pass http://localhost:3000;: Forwards requests to Gitea running on port 3000 locally.
    • proxy_set_header: Passes essential information to Gitea, like the original host, client IP, and importantly X-Forwarded-Proto $scheme which tells Gitea the original request was HTTPS.
    • WebSocket headers (Upgrade, Connection) allow Gitea's real-time UI features to work through the proxy.
    • proxy_*_timeout: Increased timeouts can prevent errors during long-running Git operations.
  • The commented-out ssl_* lines will be filled in by Certbot.
  • The map block is needed for WebSocket proxying.

5. Enable the Nginx Site and Test Configuration:

# Create a symbolic link to enable the site
sudo ln -s /etc/nginx/sites-available/gitea.yourdomain.com.conf /etc/nginx/sites-enabled/

# Remove the default Nginx site if it conflicts (optional but often needed)
# sudo rm /etc/nginx/sites-enabled/default

# Test the Nginx configuration for syntax errors
sudo nginx -t
If nginx -t reports "syntax is ok" and "test is successful", proceed. Otherwise, fix any errors reported in your config file.

6. Obtain Let's Encrypt Certificate using Certbot:

Run Certbot, telling it to use the Nginx plugin and specifying your domain. It will automatically detect your Nginx configuration, obtain the certificate, and configure the SSL settings in your Nginx file.

# Replace 'gitea.yourdomain.com' with your domain and provide your email
sudo certbot --nginx -d gitea.yourdomain.com --email your-email@example.com --agree-tos --no-eff-email
  • Certbot will ask if you want to redirect HTTP traffic to HTTPS. Choose option 2 (Redirect).
  • If successful, Certbot will confirm the certificate installation and deployment. It will also set up automatic renewal.

7. Reload Nginx to Apply Changes:

sudo systemctl reload nginx

8. Modify Gitea's app.ini: Now that Nginx is handling HTTPS, we need to tell Gitea its correct public-facing URL and that it's running behind a proxy.

# Stop Gitea to safely edit the config
sudo systemctl stop gitea

# Edit the configuration file
sudo nano /etc/gitea/app.ini

Find and update/add the following settings in the [server] section:

[server]
PROTOCOL         = http # Gitea itself still listens on HTTP
DOMAIN           = gitea.yourdomain.com
ROOT_URL         = https://gitea.yourdomain.com # The PUBLIC URL, now HTTPS!
HTTP_ADDR        = 127.0.0.1 # Listen only on localhost
HTTP_PORT        = 3000 # Keep the internal port
# Ensure SSH settings still reflect how SSH access works (usually direct, not via proxy)
SSH_DOMAIN       = gitea.yourdomain.com # Domain for SSH clone URLs
SSH_PORT         = 22
# Make sure Gitea trusts the X-Forwarded-Proto header from Nginx
# This might be implicitly trusted from localhost, but explicitly setting
# REVERSE_PROXY_TRUSTED_PROXIES might be needed in complex setups.
# Start without it, add if clone URLs inside Gitea UI are wrong (http instead of https).
# REVERSE_PROXY_TRUSTED_PROXIES = 127.0.0.1/8, ::1/128 # Trust requests from localhost

Explanation:

  • PROTOCOL = http: Gitea itself still runs on HTTP. Nginx handles the public HTTPS.
  • DOMAIN: Should match your domain.
  • ROOT_URL: Crucial! This must be the final HTTPS URL users access.
  • HTTP_ADDR = 127.0.0.1: Makes Gitea only listen for connections from the server itself (i.e., from Nginx), enhancing security. It won't be accessible directly via http://your_server_ip:3000 from the outside anymore.

Save the file (Ctrl+X, Y, Enter in nano).

9. Restart Gitea:

sudo systemctl start gitea
sudo systemctl status gitea # Verify it started correctly

10. Verify HTTPS Access:

  • Open your browser and navigate to https://gitea.yourdomain.com.
  • You should see your Gitea instance served over HTTPS, with a valid lock icon in the address bar.
  • Try logging in.
  • Check the clone URLs displayed on a repository page – they should now correctly show https://gitea.yourdomain.com/... for HTTP(S) and git@gitea.yourdomain.com:... for SSH.

You have successfully secured your Gitea instance using Nginx and Let's Encrypt!

User Management and Authentication

Gitea provides flexible options for managing users and integrating with external authentication systems.

  • Local User Management: By default (especially if you disabled self-registration), the Gitea administrator creates and manages user accounts directly through the web UI (Site Administration -> User Management). You can:
    • Create new users.
    • Edit user profiles (username, email, password, permissions - admin status).
    • Activate/deactivate or ban users.
    • Delete users.
  • Organizations and Teams: As discussed earlier, these allow for grouping users and managing repository access efficiently, especially for collaborative projects. Admins or organization owners can create organizations, add members, and form teams with specific repository permissions.
  • Authentication Sources: Gitea can authenticate users against external directories or providers, reducing the need to manage separate passwords within Gitea. This is configured in Site Administration -> Authentication Sources. Common options include:
    • LDAP: Authenticate against LDAP servers like OpenLDAP or Active Directory. Gitea supports two modes:
      • LDAP (via Bind DN): Gitea binds to the LDAP server with a dedicated service account (Bind DN and password) to search for users.
      • LDAP (Simple Auth): Gitea attempts to bind directly as the user who is trying to log in, using the provided username and password.
    • OAuth2: Allow users to log in using accounts from popular providers like GitHub, GitLab, Google, Facebook, Discord, etc. You need to register Gitea as an application with the chosen provider to get client ID and secret credentials.
    • SMTP Authentication: Authenticate users against an email server.
    • Reverse Proxy Authentication: Delegate authentication entirely to the reverse proxy (e.g., using HTTP Basic Auth or other modules), passing the authenticated username to Gitea via a request header (e.g., X-WEBAUTH-USER). Requires careful configuration of trusted headers.
  • Two-Factor Authentication (2FA): Users can enhance their account security by enabling 2FA using Time-based One-Time Passwords (TOTP) apps like Google Authenticator, Authy, etc. This is configured in individual user settings (Settings -> Security -> Manage Two-Factor Authentication). Administrators can enforce 2FA requirements globally if needed (via app.ini).

Workshop Configuring LDAP Authentication

This workshop demonstrates configuring Gitea to authenticate users against an LDAP directory using the "LDAP (via Bind DN)" method.

Prerequisites:

  • A running LDAP server (e.g., OpenLDAP). Setting up an LDAP server is beyond the scope of this guide. For testing, you might use a public test LDAP server (use with caution) or set up a simple OpenLDAP instance locally or in a container.
  • Details of your LDAP server:
    • Hostname or IP address (ldap.example.com)
    • Port (usually 389 for plain LDAP or 639 for LDAPS/StartTLS)
    • Bind DN and Password (a service account Gitea uses to search LDAP, e.g., cn=gitea-bind,ou=system,dc=example,dc=com)
    • User Search Base (where user entries are located, e.g., ou=people,dc=example,dc=com)
    • User Filter (how to find user objects, e.g., (&(objectClass=inetOrgPerson)(uid=%s))) - %s is replaced by the login username.
    • Attribute Mapping: Which LDAP attributes correspond to Gitea's username, first name, last name, and email (e.g., uid, givenName, sn, mail).

Steps:

1. Access Gitea Administration: Log in to your Gitea instance as the administrator. Navigate to Site Administration (top right avatar -> Site Administration).

2. Navigate to Authentication Sources: In the left-hand menu, click on "Authentication Sources".

3. Add New LDAP Source:

  • Click the "Add Authentication Source" button.
  • Authentication Name: Give it a descriptive name (e.g., MyCompany LDAP).
  • Authentication Type: Select LDAP (via BindDN).

4. Configure LDAP Settings: Fill in the details specific to your LDAP server. The following are examples:

  • Security Protocol: Choose Unencrypted, LDAPS, or StartTLS based on your LDAP server configuration.
  • Host: Enter the hostname or IP address of your LDAP server (e.g., ldap.example.com).
  • Port: Enter the LDAP server port (e.g., 389 or 639).
  • Bind DN: Enter the full Distinguished Name (DN) of the service account Gitea will use to connect (e.g., cn=gitea-bind,ou=system,dc=example,dc=com).
  • Bind Password: Enter the password for the Bind DN service account.
  • User Search Base: Enter the base DN where user accounts are located (e.g., ou=people,dc=example,dc=com).
  • User Filter: Enter the filter to find users. %s will be replaced by the username entered on the Gitea login page. A common filter is (&(objectClass=inetOrgPerson)(uid=%s)). Adjust inetOrgPerson and uid based on your schema.
  • Admin Filter: (Optional) An LDAP filter to identify users who should automatically be granted Gitea admin privileges (e.g., (memberOf=cn=gitea-admins,ou=groups,dc=example,dc=com)).
  • Username Attribute: The LDAP attribute containing the username (e.g., uid or sAMAccountName for Active Directory).
  • First Name Attribute: The LDAP attribute for the user's first name (e.g., givenName).
  • Last Name Attribute: The LDAP attribute for the user's last name (e.g., sn).
  • Email Attribute: The LDAP attribute for the user's email address (e.g., mail).
  • Public SSH Key Attribute: (Optional) If users store their public SSH keys in LDAP (e.g., sshPublicKey), specify the attribute here. Gitea can sync these keys.
  • Avatar Attribute: (Optional) If user photos are stored in LDAP (e.g., jpegPhoto or thumbnailPhoto), specify the attribute.

5. Enable Synchronization (Optional but Recommended):

  • Check "Enable User Synchronisation": Allows Gitea to periodically update user information (like email, name, SSH keys, admin status) from LDAP. Configure the schedule (e.g., every 12h).

6. Activate the Source:

  • Make sure the "This authentication source is activated" checkbox is checked.

7. Save the Configuration:

  • Click the "Add Authentication Source" button at the bottom.

8. Test the Login:

  • Log out of your Gitea admin account.
  • On the Gitea login page, try logging in using the username and password of a valid user from your LDAP directory.
  • If the configuration is correct, Gitea should authenticate against LDAP and log you in. If it's the first time this LDAP user logs into Gitea, an account will be automatically provisioned based on the information fetched from LDAP.

Troubleshooting:

  • If login fails, check the Gitea logs (/var/log/gitea/gitea.log or via journalctl -u gitea) for detailed error messages related to LDAP connection or search failures.
  • Double-check all LDAP parameters (Host, Port, Bind DN/Password, Search Base, Filters, Attributes). Use an external tool like ldapsearch from the Gitea server to verify connectivity and search parameters independently.
  • Verify firewall rules allow connection from the Gitea server to the LDAP server on the specified port.
  • Ensure the Bind DN account has sufficient permissions to search the user base and read the required attributes.

Git Hooks and Webhooks

Gitea allows you to trigger actions or notifications based on events happening within your repositories, primarily through Git Hooks and Webhooks.

  • Git Hooks: These are scripts that Git executes before or after certain events occur, such as pre-receive (before accepting a push), update (per-branch check during a push), and post-receive (after a successful push).
    • Server-Side Hooks: Gitea manages server-side hooks. You can configure them via the repository settings in the web UI (Settings -> Git Hooks).
    • Use Cases: Enforcing commit message policies, checking code style, preventing pushes that break tests, triggering custom deployment scripts directly on the Gitea server.
    • Editing: You can edit the content of pre-receive, update, and post-receive hooks directly in the Gitea UI. These scripts run on the Gitea server as the git user. Be cautious with what these scripts do.
  • Webhooks: These provide a more flexible way to notify external services about events in your Gitea repositories. When a configured event occurs (e.g., push, create issue, create pull request), Gitea sends an HTTP POST request with a JSON payload containing details about the event to a specified URL.
    • Configuration: Managed per-repository (Settings -> Webhooks) or globally for organizations.
    • Target URL: The URL of the external service that will receive the notification.
    • Payload Format: Gitea supports its own format, but can also emulate formats from Gogs, Slack, Discord, DingTalk, Telegram, Microsoft Teams, etc., for easier integration.
    • Trigger Events: You can select which events trigger the webhook (e.g., push only, issues, pull requests, releases).
    • Secret: You can configure a secret token. Gitea will use it to generate a hash signature of the payload (e.g., X-Gitea-Signature) included in the request headers. The receiving service can verify this signature to ensure the request genuinely came from Gitea.
    • Use Cases: Triggering CI/CD pipelines (Jenkins, Drone, GitLab CI Runner via webhook), sending notifications to chat platforms (Slack, Discord), updating external issue trackers, automating deployments.

Workshop Setting up a Post-Receive Notification Webhook

This workshop demonstrates setting up a simple webhook that notifies a basic Python Flask application whenever code is pushed to a specific Gitea repository.

Prerequisites:

  • Python 3 and pip installed on a machine accessible from the Gitea server (this could be the Gitea server itself, or another machine).
  • A Gitea repository to test with (e.g., the my-first-repo created earlier).

Steps:

1. Create a Simple Webhook Receiver (Python Flask App): On the machine where you'll run the receiver:

  • Install Flask:
    python3 -m pip install Flask
    
  • Create the App File: Create a file named webhook_receiver.py.
    import json
    from flask import Flask, request, abort
    
    app = Flask(__name__)
    
    # Optional: Define the secret configured in Gitea for verification
    # GITEA_SECRET = "your_very_secret_token" # Keep this secure!
    
    @app.route('/webhook', methods=['POST'])
    def handle_gitea_webhook():
        # Optional: Verify the signature if a secret is configured
        # signature = request.headers.get('X-Gitea-Signature')
        # if not signature:
        #     print("WARN: Request missing signature header.")
        #     # Decide if you want to abort or process anyway for testing
        #     # abort(400, 'Request missing signature header')
        # # Implement signature verification logic here using GITEA_SECRET
        # # See Gitea documentation for how the signature is calculated (HMAC-SHA256)
    
        if request.headers.get('Content-Type') == 'application/json':
            payload = request.json
            print("Received Gitea Webhook:")
            # Pretty print the JSON payload
            print(json.dumps(payload, indent=4))
    
            # --- Add your custom logic here! ---
            # Example: Print information about the push
            if request.headers.get('X-Gitea-Event') == 'push':
                pusher = payload.get('pusher', {}).get('login', 'Unknown')
                repo_name = payload.get('repository', {}).get('name', 'Unknown')
                branch = payload.get('ref', 'refs/heads/unknown').split('/')[-1]
                print(f"-> Detected push by '{pusher}' to '{repo_name}' branch '{branch}'")
                # You could trigger a build script, send a notification, etc.
            # --- End of custom logic ---
    
            return '', 204 # Return success (204 No Content is often appropriate)
        else:
            return 'Unsupported Media Type', 415
    
    if __name__ == '__main__':
        # Run on host 0.0.0.0 to be accessible externally
        # Choose a port (e.g., 5000)
        app.run(host='0.0.0.0', port=5000, debug=True) # Use debug=False in production
    
  • Run the App:
    python3 webhook_receiver.py
    
    The application will start listening on port 5000 (or whichever port you chose). Note the IP address or domain name of the machine running this script. You'll need it for the Target URL. Ensure the firewall on this machine allows incoming connections on port 5000.

2. Configure the Webhook in Gitea:

  • Log in to Gitea and navigate to the repository you want to monitor (e.g., my-first-repo).
  • Go to Settings -> Webhooks.
  • Click "Add Webhook" and choose "Gitea".
  • Target URL: Enter the URL of your running Flask app, including the /webhook endpoint (e.g., http://receiver_machine_ip:5000/webhook). Replace receiver_machine_ip with the actual IP or domain.
  • HTTP Method: Keep POST.
  • POST Content Type: Select application/json.
  • Secret: (Optional) Enter a strong secret token (e.g., your_very_secret_token). If you set this, uncomment and set the GITEA_SECRET variable in your Flask app and implement signature verification. For this simple test, you can leave it blank.
  • Trigger On: Select "Choose from a list of events". Uncheck "Create" and keep only "Push" checked. You can customize this based on your needs.
  • Branch Filter: (Optional) Specify a branch if you only want pushes to that branch to trigger the webhook (e.g., main). Leave blank for all branches.
  • Active: Ensure the checkbox is checked.
  • Click "Add Webhook".

3. Test the Webhook:

  • Gitea might offer a "Test Delivery" button after creating the webhook. Click it to send a sample event. Check the console output of your webhook_receiver.py script. You should see the sample payload printed.
  • Now, perform a real test. Clone the repository locally (if you haven't already), make a change, commit, and push:
    # On your local machine, inside the repository directory
    echo "Testing webhook $(date)" >> test_webhook.txt
    git add test_webhook.txt
    git commit -m "Test Gitea webhook"
    git push origin main # Or your default branch name
    
  • Watch the console output of your webhook_receiver.py script. When the push completes successfully, Gitea should send a POST request, and you should see the JSON payload printed, along with the custom message indicating the push event details.

4. Review Delivery History:

  • Back in the Gitea Webhooks settings page, you can see the delivery history for your webhook. Click on a delivery attempt to see the request headers, payload, and the response received from your Flask application. This is very useful for debugging.

You have now successfully set up a Gitea webhook to trigger an external action upon code pushes! This forms the basis for many CI/CD and automation workflows.

Exploring Gitea Features

Beyond basic hosting, Gitea offers features to support the software development lifecycle:

  • Issue Tracking: Each repository has its own issue tracker (Issues tab). You can:
    • Create issues with descriptions, assignees, labels, milestones, and due dates.
    • Use Markdown for formatting.
    • Mention other users (@username) or reference other issues (#issue_number).
    • Track dependencies between issues.
    • Create custom labels (e.g., bug, feature, documentation, priority:high) to categorize issues.
    • Group issues into milestones (e.g., Version 1.0, Sprint 3).
    • Filter and search issues based on various criteria.
  • Pull Requests (PRs) and Code Review: Gitea facilitates collaborative development through pull requests.
    • Developers work on features or fixes in separate branches.
    • When ready, they create a Pull Request to merge their branch into a target branch (e.g., main).
    • Team members can review the code changes (diff view), leave comments on specific lines or general feedback, and approve the changes.
    • Gitea shows merge conflicts and checks if the PR can be merged automatically.
    • Once approved, the PR can be merged (merge commit, rebase, or squash merge) via the web UI by authorized users.
    • Issues can be automatically closed by mentioning them in PR commit messages (e.g., Fixes #123).
  • Releases and Tags: You can create formal releases based on Git tags (Repository -> Tags -> New Release).
    • Associate release notes (using Markdown) and attach build artifacts (e.g., compiled binaries, installers) to a specific tag.
    • This provides a clear way to distribute software versions.
  • Wikis: Each repository can have an integrated wiki (Wiki tab) for documentation, project notes, or user guides. Wikis are typically backed by a separate Git repository and use Markdown for content creation.
  • Migrations: Gitea includes a built-in tool to migrate repositories from other platforms like GitHub, GitLab, Bitbucket, Gogs, or even plain Git URLs. This tool can often migrate not just the code history, but also issues, pull requests, milestones, labels, and releases, making it easier to switch to your self-hosted Gitea instance. Access via the + icon -> New Migration.

Workshop Migrating a GitHub Repository

Let's migrate an existing public repository from GitHub to your Gitea instance, including its issues and labels.

Prerequisites:

  • A GitHub account.
  • A Personal Access Token (PAT) from GitHub with appropriate permissions (at least repo scope for public/private repos, potentially admin:org for organization repos if needed).
  • The URL of the GitHub repository you want to migrate (we'll use a public example).

Steps:

1. Generate a GitHub Personal Access Token (PAT):

  • Go to your GitHub account settings -> Developer settings -> Personal access tokens -> Tokens (classic).
  • Click "Generate new token" -> "Generate new token (classic)".
  • Note: Give your token a descriptive name (e.g., gitea-migration).
  • Expiration: Choose an appropriate expiration date.
  • Select scopes: Check the repo scope. This grants access to public and private repositories (code, commits, branches, etc.). For migrating issues, pull requests, etc., you might need additional scopes depending on the repository and what you want to migrate, but repo is often sufficient for basic code + issue migration from public repos. Review GitHub's documentation if migrating private data or from organizations.
  • Click "Generate token".
  • Important: Copy the generated token immediately and save it somewhere secure. You won't be able to see it again after leaving the page.

2. Start Migration in Gitea:

  • Log in to your Gitea instance.
  • Click the + icon in the top right corner and select "New Migration".

3. Configure Migration Source:

  • Migrate From: Select GitHub.
  • Clone URL: Enter the HTTPS clone URL of the GitHub repository you want to migrate. For example, let's migrate the popular ohmyzsh repository (or choose any other public repository): https://github.com/ohmyzsh/ohmyzsh.git
  • Authentication Token: Paste the GitHub Personal Access Token you generated in Step 1.

4. Configure Gitea Destination:

  • Repository Owner: Select the Gitea user or organization that will own the migrated repository (e.g., your admin user).
  • Repository Name: Choose a name for the repository on your Gitea instance (e.g., ohmyzsh-migrated).
  • Private: Decide if the new repository on Gitea should be private or public.
  • Description: The description will usually be fetched automatically, but you can override it.

5. Select Items to Migrate:

  • LFS: Check if the source repository uses Git LFS and you want to migrate LFS files.
  • Wiki: Check if you want to migrate the repository's wiki.
  • Milestones: Check to migrate milestones.
  • Labels: Check to migrate labels.
  • Issues: Check to migrate issues.
  • Pull Requests: Check to migrate pull requests (may require more PAT permissions depending on context).
  • Releases: Check to migrate releases/tags.
  • Collaboration: (Usually leave unchecked unless migrating internal GitHub metadata relevant only within GitHub).

    For this workshop, let's check: Wiki, Milestones, Labels, Issues, Releases.**

6. Start the Migration:

  • Click the "Migrate Repository" button.
  • Gitea will start the migration process in the background. You'll be redirected to the new repository's page, which might initially be empty or show a progress indicator. The migration can take time depending on the size of the repository and the amount of data (issues, etc.) being migrated. You can monitor the progress usually via notifications or by refreshing the page.

7. Verify the Migrated Repository:

  • Once the migration completes (you should see the code files appear), explore the repository in Gitea:
    • Check the Code tab for branches and commit history.
    • Check the Issues tab. You should see the migrated issues with their original numbers (usually), labels, and potentially assignees/comments if the PAT had sufficient permissions and users can be mapped.
    • Check the Pull Requests tab (if migrated).
    • Check Releases/Tags.
    • Check Settings -> Labels to see the migrated labels.
    • Check the Wiki tab.

You have successfully migrated a repository from GitHub to your self-hosted Gitea instance, preserving valuable metadata beyond just the code history. This feature is extremely useful when consolidating projects onto your own server.

3. Advanced Administration and Customization

This section delves into more advanced topics for managing and tailoring your Gitea instance. We'll cover crucial operational tasks like backups and restores, optimizing performance, understanding high availability concepts, customizing the look and feel, and diving deeper into the app.ini configuration file.

Backup and Restore Strategies

Regular backups are non-negotiable for any important service, including your Gitea instance. A failure (hardware, software bug, human error) could lead to the loss of valuable code history, issues, and configurations.

What Needs Backing Up?

A complete Gitea backup requires several components:

  1. Database: Contains all metadata – users, permissions, issue tracking, pull requests, comments, repository information (names, descriptions, settings), LFS pointers, webhook configurations, etc. The method depends on your chosen database (SQLite3 file copy, pg_dump for PostgreSQL, mysqldump for MySQL/MariaDB).
  2. Configuration File: The app.ini file (/etc/gitea/app.ini in our setup) contains all your Gitea settings.
  3. Git Repositories: The actual Git data stored on the filesystem (e.g., under /var/lib/gitea/data/gitea-repositories). These are bare Git repositories.
  4. Gitea Data Directory (data/): Other essential data stored within /var/lib/gitea/data/ (or your configured GITEA_WORK_DIR/data), including:
    • Attachments: Files uploaded to issues or pull requests.
    • Avatars: User/organization profile pictures (if not using Gravatar).
    • LFS Objects: Large files managed by Git LFS (if used).
    • Indexers: Search index files (for code, issues).
    • Packages: Package registry files (if used).
    • Sessions: Active user session data (less critical to back up frequently, but part of the work dir).
  5. Logs (/var/log/gitea): While not strictly required for restore, backing up logs can be crucial for auditing or troubleshooting past issues. Their backup frequency might be lower.
  6. Custom Files (/var/lib/gitea/custom): Any customizations (templates, public files) you've made.

Using the gitea dump Command (Recommended Method):

Gitea provides a convenient command-line tool to perform a consistent backup of most essential components.

  • How it Works: The gitea dump command typically:
    • Briefly puts Gitea into maintenance mode (if running).
    • Dumps the database to an SQL file (for PostgreSQL/MySQL) or copies the file (for SQLite3).
    • Archives the configuration file (app.ini).
    • Archives the repository files.
    • Archives other data directories (attachments, LFS, avatars, etc.).
    • Archives the log files.
    • Packages everything into a single compressed archive (usually a .zip file).
  • Command Syntax:
    sudo -u git /usr/local/bin/gitea dump --config /etc/gitea/app.ini --tempdir /path/to/temp/storage --file /path/to/backup/gitea-backup.zip
    
    • sudo -u git: Crucial! Run the command as the git user to ensure correct file permissions during the dump process, especially for repositories.
    • /usr/local/bin/gitea: Path to your Gitea executable.
    • dump: The subcommand for creating a backup.
    • --config /etc/gitea/app.ini: Specifies the configuration file Gitea should use to find database settings, repository paths, etc.
    • --tempdir /path/to/temp/storage: (Optional but Recommended) Specifies a temporary directory with enough free space to stage the backup files before zipping. Defaults to the system temp dir, which might be too small. Choose a location on a volume with ample space. Ensure the git user can write here or run dump as root (less ideal).
    • --file /path/to/backup/gitea-backup.zip: (Optional) Specifies the output zip file name and location. Defaults to gitea-dump-<timestamp>.zip in the directory where the command is run.
  • Running: It's best practice to stop the Gitea service before running gitea dump to ensure data consistency, although the command attempts to handle a running instance.
    sudo systemctl stop gitea
    sudo mkdir -p /opt/gitea-backups # Create a dedicated backup directory
    sudo chown git:git /opt/gitea-backups # Ensure git user can write here (if specifying --file)
    sudo mkdir -p /mnt/backup-staging # Example temporary directory
    sudo chown git:git /mnt/backup-staging
    sudo -u git /usr/local/bin/gitea dump --config /etc/gitea/app.ini --tempdir /mnt/backup-staging --file /opt/gitea-backups/gitea-backup-$(date +%Y%m%d-%H%M%S).zip
    sudo systemctl start gitea
    

Manual Backup Procedures:

If gitea dump isn't suitable or you need more control:

  1. Stop Gitea: sudo systemctl stop gitea (Essential for consistency).
  2. Backup Database:
    • SQLite3: sudo cp /var/lib/gitea/data/gitea.db /path/to/backup/gitea.db.backup
    • PostgreSQL: sudo -u postgres pg_dump -U gitea_user -Fc gitea_db_name > /path/to/backup/gitea_db.pgdump (Replace user/db name, -Fc creates a custom, compressed format).
    • MySQL/MariaDB: sudo mysqldump -u gitea_user -p gitea_db_name | gzip > /path/to/backup/gitea_db.sql.gz (Replace user/db name).
  3. Backup Filesystem Data: Use rsync or tar to copy the relevant directories.
    # Example using tar
    sudo tar czvf /path/to/backup/gitea_config_$(date +%Y%m%d).tar.gz /etc/gitea
    sudo tar czvf /path/to/backup/gitea_data_$(date +%Y%m%d).tar.gz /var/lib/gitea
    sudo tar czvf /path/to/backup/gitea_logs_$(date +%Y%m%d).tar.gz /var/log/gitea
    
    Using rsync is often better for subsequent backups as it only copies changed files.
    sudo rsync -avz --delete /etc/gitea/ /path/to/backup/config/
    sudo rsync -avz --delete /var/lib/gitea/ /path/to/backup/data/
    sudo rsync -avz --delete /var/log/gitea/ /path/to/backup/logs/
    
  4. Start Gitea: sudo systemctl start gitea

Restoration Process:

Restoring requires careful steps, ideally on a fresh server or after cleaning the old installation state. Always test your restore process periodically!

  • Using gitea dump Backup:

    1. Install Prerequisites: Set up a new server with Git, the chosen database (if not SQLite3), and create the git user. Install the same version of Gitea that the backup was taken from.
    2. Prepare Directories: Create the necessary empty directories (/etc/gitea, /var/lib/gitea, /var/log/gitea) and set permissions (chown git:git, chmod).
    3. Unzip Backup: Extract the gitea-backup-....zip file to a temporary location.
      unzip gitea-backup-....zip -d /tmp/gitea-restore
      
    4. Restore Database:
      • SQLite3: Copy the gitea.db file from the extracted backup's data directory to /var/lib/gitea/data/gitea.db. Ensure permissions are correct.
      • PostgreSQL: Create an empty Gitea database and user, then restore using pg_restore.
        # Example commands (run as postgres user or sudo -u postgres)
        createdb -U gitea_user gitea_db_name
        pg_restore -U gitea_user -d gitea_db_name /tmp/gitea-restore/gitea-db.sql # Path from extracted dump
        
      • MySQL/MariaDB: Create an empty Gitea database and user, then restore using mysql.
        # Example commands (run as root or user with create privileges)
        mysql -u root -p -e "CREATE DATABASE gitea_db_name CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_unicode_ci';"
        mysql -u root -p -e "GRANT ALL PRIVILEGES ON gitea_db_name.* TO 'gitea_user'@'localhost' IDENTIFIED BY 'your_password';"
        mysql -u root -p -e "FLUSH PRIVILEGES;"
        gunzip < /tmp/gitea-restore/gitea-db.sql.gz | mysql -u gitea_user -p gitea_db_name # Path from extracted dump
        
    5. Restore Files: Copy the contents of the extracted directories (app.ini, gitea-repositories, data/*, log/*, custom/*) to their corresponding locations on the server (/etc/gitea/app.ini, /var/lib/gitea/data/gitea-repositories, /var/lib/gitea/data, /var/log/gitea, /var/lib/gitea/custom).
      # Example using rsync after unzipping to /tmp/gitea-restore
      sudo rsync -avz /tmp/gitea-restore/app.ini /etc/gitea/app.ini
      sudo rsync -avz --delete /tmp/gitea-restore/gitea-repositories/ /var/lib/gitea/data/gitea-repositories/
      sudo rsync -avz --delete /tmp/gitea-restore/data/ /var/lib/gitea/data/
      sudo rsync -avz --delete /tmp/gitea-restore/log/ /var/log/gitea/
      # Add custom dir if it exists in backup
      # sudo rsync -avz --delete /tmp/gitea-restore/custom/ /var/lib/gitea/custom/
      
    6. Set Permissions: Double-check and reset ownership and permissions on all restored files and directories to match the original setup (owned by git:git with appropriate chmod values). This is critical!
      sudo chown -R git:git /etc/gitea /var/lib/gitea /var/log/gitea
      sudo chmod 750 /etc/gitea
      sudo chmod 770 /var/lib/gitea /var/log/gitea
      # Permissions within /var/lib/gitea might need finer grain adjustment,
      # but Gitea often fixes them on startup if ownership is correct.
      
    7. Start Gitea: sudo systemctl start gitea
    8. Test Thoroughly: Log in, check repositories, issues, settings, etc.
  • Restoring from Manual Backup: Follow similar steps, but extract/copy data from your tar or rsync backups instead of the gitea dump zip file.

Automation:

Use cron jobs or systemd timers to schedule your backup script (whether using gitea dump or manual commands) to run regularly (e.g., nightly). Ensure backups are stored securely, ideally off-site or on separate storage.

# Example cron job (edit with `sudo crontab -e`)
# Run backup daily at 2:30 AM
# 30 2 * * * /path/to/your/gitea_backup_script.sh >> /var/log/gitea_backup.log 2>&1

Workshop Performing a Full Backup and Restore using gitea dump

This workshop simulates a disaster recovery scenario using gitea dump. Caution: This involves renaming/deleting live data directories and dropping the database. Perform this on a non-critical test instance or be absolutely sure you have a working backup first.

Prerequisites:

  • A running Gitea instance (preferably a test one for this workshop).
  • Sufficient disk space for the backup zip and temporary files.
  • Using SQLite3 database for simplicity in this example.

Steps:

1. Perform the Backup:

# Stop Gitea
sudo systemctl stop gitea

# Create backup and temporary directories
sudo mkdir -p /opt/gitea-backups
sudo mkdir -p /mnt/gitea-temp
sudo chown git:git /opt/gitea-backups /mnt/gitea-temp

# Run the dump command as the 'git' user
BACKUP_FILE="/opt/gitea-backups/gitea-backup-$(date +%Y%m%d-%H%M%S).zip"
echo "Starting Gitea dump to $BACKUP_FILE"
sudo -u git /usr/local/bin/gitea dump --config /etc/gitea/app.ini --tempdir /mnt/gitea-temp --file "$BACKUP_FILE"

# Check if the backup file was created successfully
ls -lh "$BACKUP_FILE"

# Optional: Inspect the contents of the zip file
# unzip -l "$BACKUP_FILE"

# Keep Gitea stopped for the simulation
# Do NOT start Gitea yet.

2. Simulate Disaster (Rename Data and Config): WARNING: This makes your Gitea instance unusable until restored.

echo "Simulating disaster: Renaming Gitea data and config..."
sudo mv /etc/gitea /etc/gitea.bak.$(date +%s)
sudo mv /var/lib/gitea /var/lib/gitea.bak.$(date +%s)
sudo mv /var/log/gitea /var/log/gitea.bak.$(date +%s)

# Verify directories are gone (or renamed)
ls -ld /etc/gitea /var/lib/gitea /var/log/gitea # Should show 'No such file or directory'
(If using PostgreSQL/MySQL, you would also drop the Gitea database and user here)

3. Prepare for Restore: Recreate the basic structure Gitea expects.

echo "Preparing directories for restore..."
sudo mkdir -p /etc/gitea /var/lib/gitea/data /var/log/gitea # Note: data subdir is created
sudo chown -R git:git /etc/gitea /var/lib/gitea /var/log/gitea
sudo chmod 750 /etc/gitea
sudo chmod 770 /var/lib/gitea /var/log/gitea
sudo chmod 770 /var/lib/gitea/data # Ensure data dir is also writable

4. Unzip the Backup:

echo "Extracting backup file $BACKUP_FILE..."
sudo mkdir -p /tmp/gitea-restore
sudo chown git:git /tmp/gitea-restore
sudo -u git unzip "$BACKUP_FILE" -d /tmp/gitea-restore

5. Restore Files and Database: (Since we used SQLite3, restoring the database is part of copying the data directory contents)

echo "Restoring files..."
# Restore configuration
sudo -u git cp /tmp/gitea-restore/app.ini /etc/gitea/app.ini

# Restore repositories (use rsync for efficiency)
sudo rsync -avz --chown=git:git /tmp/gitea-restore/gitea-repositories/ /var/lib/gitea/data/gitea-repositories/

# Restore other data (including SQLite DB, LFS, attachments etc.)
sudo rsync -avz --chown=git:git /tmp/gitea-restore/data/ /var/lib/gitea/data/

# Restore logs
sudo rsync -avz --chown=git:git /tmp/gitea-restore/log/ /var/log/gitea/

# Restore custom directory if it exists
if [ -d "/tmp/gitea-restore/custom" ]; then
    echo "Restoring custom directory..."
    sudo mkdir -p /var/lib/gitea/custom
    sudo chown git:git /var/lib/gitea/custom
    sudo rsync -avz --chown=git:git /tmp/gitea-restore/custom/ /var/lib/gitea/custom/
fi

6. Verify Permissions (Crucial): Although rsync with --chown helps, it's good to double-check critical top-level directories.

sudo chown -R git:git /etc/gitea /var/lib/gitea /var/log/gitea
sudo chmod 750 /etc/gitea
sudo chmod 770 /var/lib/gitea /var/log/gitea # Gitea will manage sub-permissions
ls -ld /etc/gitea /var/lib/gitea /var/log/gitea # Check ownership and permissions
ls -l /var/lib/gitea/data # Check ownership of restored data

7. Start Gitea and Test:

echo "Starting Gitea after restore..."
sudo systemctl start gitea
sleep 5 # Give it a moment to start
sudo systemctl status gitea
  • Check the status and logs (sudo journalctl -u gitea -n 50) for errors.
  • Access your Gitea instance via the web browser.
  • Log in with your previous user.
  • Check if your repositories are present and contain the correct history.
  • Check if issues, labels, settings, etc., are restored.

8. Cleanup: Once you are sure the restore was successful, you can remove the simulated disaster .bak directories and the temporary restore/backup files if desired.

# sudo rm -rf /etc/gitea.bak.* /var/lib/gitea.bak.* /var/log/gitea.bak.*
# sudo rm -rf /tmp/gitea-restore
# Consider moving the backup zip file to long-term storage

This workshop demonstrates the critical process of backing up using gitea dump and, more importantly, validating the restore procedure.

Performance Tuning and Monitoring

While Gitea is lightweight, performance can degrade under heavy load or with suboptimal configuration. Tuning involves adjusting Gitea settings and ensuring the underlying system is healthy.

Analyzing app.ini for Performance:

Edit /etc/gitea/app.ini (stop Gitea first) and review these sections:

  • [database]:
    • DB_TYPE: Ensure you're using PostgreSQL or MySQL/MariaDB for larger instances, not SQLite3.
    • MAX_OPEN_CONNS, MAX_IDLE_CONNS: Adjust database connection pool sizes based on expected load and database server capacity. Too few can cause waits; too many can exhaust database resources. Start with defaults and adjust based on monitoring.
    • CONN_MAX_LIFETIME: How long connections can be reused.
    • LOG_SQL: Enable temporarily (true) for debugging database queries, but disable (false) in production as it adds significant overhead.
  • [indexer]:
    • ISSUE_INDEXER_TYPE, REPO_INDEXER_TYPE: Controls the type of indexer used for searching issues and code. bleve (default) is generally good. Indexing consumes resources; ensure paths (ISSUE_INDEXER_PATH, REPO_INDEXER_PATH) are on reasonably fast storage. You can disable indexers if search isn't needed, saving resources.
    • UPDATE_BUFFER_LEN, MAX_FILE_SIZE: Affect indexing performance and resource usage.
  • [queue] / [picture] / [attachment] / [repo.upload] etc.:
    • Gitea uses background queues for tasks like checking pull requests, rendering markdown, processing uploads. Settings like LENGTH, BATCH_LENGTH, WORKERS control the size and concurrency of these queues. Increasing workers can improve throughput but uses more CPU/RAM. Monitor queue lengths if Gitea feels sluggish during certain operations.
  • [cache]:
    • ENABLED: Ensure caching is enabled (true).
    • ADAPTER: Controls where cache data is stored (memory, redis, memcache). memory is simplest but doesn't persist across restarts and isn't shared if you scale horizontally. Using Redis or Memcached can significantly improve performance, especially with multiple Gitea instances or high traffic, by providing a shared, persistent cache.
    • INTERVAL: Cache garbage collection interval.
    • ITEM_TTL: Default time-to-live for cache items.
  • [server]:
    • DISABLE_SSH: If you only use HTTP/S access, disabling the built-in SSH server might save minimal resources (though usually negligible). If using system SSH, this has no effect.

System-Level Tuning:

  • File Descriptors: Gitea, Git operations, and the web server can consume many file descriptors. Check the limits for the git user and system-wide. Increase if necessary (/etc/security/limits.conf or systemd service file LimitNOFILE=).
    # Check current limit for running Gitea process (find PID first)
    prlimit --pid <GITEA_PID> --nofile
    # To set in systemd service file (add within [Service]):
    # LimitNOFILE=65535
    
  • Kernel Parameters: Adjust network stack parameters (sysctl.conf) like net.core.somaxconn, net.ipv4.tcp_tw_reuse if handling very high connection counts. Usually only needed for very large instances.
  • CPU/RAM/Disk IO: Monitor server resources using tools like top, htop, vmstat, iostat. Upgrade hardware or optimize storage if Gitea is consistently bottlenecked. Ensure the database server (if separate) is also adequately resourced.
  • Git Version: Keep the system's Git installation updated (git --version). Newer Git versions often include performance improvements.
  • Database Tuning: Optimize the database server itself (PostgreSQL postgresql.conf, MySQL/MariaDB my.cnf). This is a complex topic but can yield significant gains (e.g., tuning buffer pool sizes, query caches, connection limits).

Monitoring:

  • Gitea Logs: Check /var/log/gitea/gitea.log, /var/log/gitea/xorm.log (if SQL logging enabled), and journalctl -u gitea for errors or warnings indicating performance issues.
  • Server Metrics: Use standard Linux monitoring tools (top, htop, iotop, vmstat, iftop) or comprehensive systems like Prometheus + Node Exporter + Grafana to track CPU, RAM, disk I/O, and network usage over time.
  • Gitea Metrics Endpoint: Gitea can expose internal metrics in Prometheus format. This provides detailed insights into Go runtime stats, HTTP request rates/durations, queue lengths, cache performance, etc.
    • Enable in app.ini:
      [metrics]
      ENABLED = true
      TOKEN = "your_secure_metrics_token" # Protect this endpoint!
      
    • Restart Gitea.
    • Access via http://localhost:3000/metrics (usually protected by the token).
    • Configure Prometheus to scrape this endpoint. Use Grafana with a Gitea dashboard template to visualize the metrics.

Workshop Enabling and Viewing Basic Metrics

This workshop shows how to enable Gitea's Prometheus metrics endpoint and perform a basic query using curl. Setting up a full Prometheus/Grafana stack is outside this scope but mentioned as the next step.

Steps:

1. Configure app.ini for Metrics:

# Stop Gitea
sudo systemctl stop gitea

# Edit configuration
sudo nano /etc/gitea/app.ini

  • Add or modify the [metrics] section:

    [metrics]
    ENABLED = true
    # Generate a strong random token for security
    TOKEN = "REPLACE_WITH_A_VERY_STRONG_RANDOM_TOKEN"
    
    You can generate a token using commands like openssl rand -hex 32.

  • Save the file and exit.

2. Restart Gitea:

sudo systemctl start gitea
sudo systemctl status gitea # Verify startup

3. Access Metrics Endpoint using curl:

  • From the Gitea server itself (or another machine if port 3000 is accessible and firewall allows), use curl to fetch the metrics. Replace the placeholder token with the one you set.

    # Replace YOUR_GITEA_TOKEN with the actual token
    curl -H "Authorization: token YOUR_GITEA_TOKEN" http://localhost:3000/metrics
    
  • Expected Output: You should see a large text output starting with lines like # HELP go_gc_duration_seconds ... followed by many metric names and their current values (e.g., gitea_http_request_duration_seconds_count{code="200",method="get",path="/"} 42). This is the Prometheus exposition format.

4. Interpret Basic Metrics:

Look through the output for metrics like:

  • process_resident_memory_bytes: Memory usage.
  • go_goroutines: Number of concurrent Go routines (indicates internal concurrency).
  • gitea_http_requests_total: Counter for total HTTP requests handled.
  • gitea_http_request_duration_seconds_bucket: Histogram measuring HTTP request latency.
  • gitea_queue_task_total{name="..."}: Counters for tasks processed by background queues.

Next Steps (Conceptual):

  1. Install Prometheus on a monitoring server (or the Gitea server itself for simple setups).
  2. Configure Prometheus (prometheus.yml) to scrape the Gitea metrics endpoint, providing the bearer token:
    scrape_configs:
      - job_name: 'gitea'
        bearer_token: 'YOUR_GITEA_TOKEN' # Or use bearer_token_file
        static_configs:
          - targets: ['localhost:3000'] # Or gitea.yourdomain.com:3000 if scraping remotely
    
  3. Install Grafana.
  4. Add Prometheus as a data source in Grafana.
  5. Import a pre-built Gitea dashboard template (search the Grafana dashboards website) or create your own panels to visualize key metrics like request rates, error rates, latencies, resource usage, and queue statistics.

Enabling metrics provides invaluable visibility for understanding performance bottlenecks and overall system health.

High Availability (Conceptual)

Achieving true high availability (HA) for Gitea, meaning resilience to single component failures with minimal downtime, is complex and significantly increases infrastructure requirements. This section provides a conceptual overview.

Challenges:

  • Shared Storage: Git repositories, LFS data, attachments, avatars, etc., must be accessible and consistent across all active Gitea instances.
  • Database: The database becomes a single point of failure unless made highly available itself (clustering, replication).
  • Session Management: User sessions need to be shared or managed consistently across instances. Using a shared cache like Redis helps.
  • Queue Consistency: Background queue tasks need to be handled correctly in a multi-instance setup, potentially requiring a shared message broker (like Redis or RabbitMQ) if Gitea's default queue isn't cluster-aware (check current Gitea docs).
  • Configuration Consistency: app.ini must be consistent across all instances.
  • Load Balancing: A load balancer is needed to distribute incoming user traffic (HTTP/S and potentially SSH) across the active Gitea nodes.

Common Architectures (Simplified):

  1. Active/Passive:

    • One primary Gitea instance handles all traffic.
    • A secondary instance is kept idle but ready to take over.
    • Requires:
      • Shared storage (e.g., NFS, GlusterFS, CephFS mounted at /var/lib/gitea).
      • Database replication (e.g., PostgreSQL streaming replication) from active to passive.
      • A mechanism (e.g., Keepalived with VRRP, DNS failover) to switch traffic (or a floating IP) to the passive node if the active one fails.
    • Simpler than Active/Active, but failover might involve brief downtime.
  2. Active/Active:

    • Multiple Gitea instances actively serve traffic simultaneously.
    • Requires:
      • Load Balancer (e.g., HAProxy, Nginx) distributing traffic to healthy nodes.
      • Shared storage (NFS, GlusterFS, CephFS).
      • A clustered or highly available database backend (e.g., PostgreSQL with Patroni/Pgpool-II, MySQL/MariaDB Galera Cluster).
      • Shared caching (Redis/Memcached) for sessions and application cache.
      • Potentially a cluster-aware queue system if built-in queues aren't sufficient.
    • Provides better scalability and potentially seamless failover, but significantly more complex to set up and manage.

Tools Mentioned:

  • Shared Filesystems: NFS (simpler, potential single point of failure at NFS server), GlusterFS (distributed), CephFS (distributed, complex). Performance characteristics vary.
  • Database HA: PostgreSQL Streaming Replication, Patroni, Pgpool-II, TimescaleDB Multi-node; MySQL/MariaDB Replication, Galera Cluster, InnoDB Cluster.
  • Load Balancers/Failover: HAProxy, Nginx (as LB), Keepalived (for IP failover).
  • Shared Cache/Queue: Redis (often used for both), Memcached, RabbitMQ.

Conclusion on HA: Implementing Gitea HA requires careful planning, robust infrastructure, and significant operational expertise. For most small to medium deployments, focusing on reliable backups, monitoring, and a solid single-server setup (potentially with vertical scaling) is often more practical.

Customizing Gitea Look and Feel

You can customize Gitea's appearance to match your branding or preferences without modifying the core Gitea code. This is done by placing override files in a special custom directory.

Location of Custom Files:

The custom directory is typically located within Gitea's work directory (GITEA_WORK_DIR), which we set up as /var/lib/gitea. So, the path would be: /var/lib/gitea/custom

You may need to create this directory:

sudo mkdir -p /var/lib/gitea/custom
sudo chown git:git /var/lib/gitea/custom
sudo chmod 755 /var/lib/gitea/custom

Customization Areas:

  1. Public Files (CSS, JS, Images):

    • Place static assets like custom logos, favicons, CSS stylesheets, or JavaScript files inside /var/lib/gitea/custom/public/.
    • Subdirectories like custom/public/img, custom/public/css, custom/public/js are common.
    • Logo: Place your logo (e.g., logo.png, logo.svg) in custom/public/img/. Gitea often automatically detects and uses logo.png or logo.svg from here. Check app.ini [ui] settings if specific filenames are needed.
    • Favicon: Place favicon.ico or favicon.png in custom/public/img/.
    • Custom CSS: Create a CSS file (e.g., custom/public/css/theme.css). Reference it in custom templates or potentially via app.ini settings if available (check documentation for keys like [ui] CUSTOM_CSS).
    • Custom JS: Similar to CSS, place JS files in custom/public/js/ and include them via custom templates.
  2. Overriding Templates:

    • Gitea uses Go templates for its HTML pages. You can override almost any template by placing a modified version in the corresponding path under /var/lib/gitea/custom/templates/.
    • To find the original template path, browse the Gitea source code (templates/ directory on GitHub: https://github.com/go-gitea/gitea/tree/main/templates).
    • Example: To customize the footer across all pages, find the original templates/base/footer.tmpl. Create the directory custom/templates/base/ and copy the original footer.tmpl into it. Modify /var/lib/gitea/custom/templates/base/footer.tmpl as needed.
    • Example: To add custom content specifically to the footer (without replacing the whole thing), create a file named custom/templates/custom/footer.tmpl. Gitea includes this specific file at the end of the standard footer if it exists. This is often easier than overriding the entire base/footer.tmpl.
    • Example: Modify the landing page (homepage for non-logged-in users): Find templates/home.tmpl, copy it to custom/templates/home.tmpl, and edit.
  3. Mail Templates: Override email notification templates in custom/templates/mail/.

  4. Configuration (app.ini): Some UI elements (like Site Title, landing page visibility) are controlled directly via app.ini settings in the [ui] section.

Applying Changes: After adding or modifying files in the custom directory, you usually need to restart the Gitea service for the changes to take effect.

sudo systemctl restart gitea
Also, clear your browser cache to ensure you're seeing the updated assets and templates.

Let's replace the default Gitea logo and add custom text to the footer.

Prerequisites:

  • A custom logo image file (e.g., my-logo.png or my-logo.svg). For testing, you can download any small image and rename it. Ensure it's reasonably sized for a web logo.
  • Gitea running.

Steps:

1. Prepare Custom Directory Structure: Ensure the custom directory and necessary subdirectories exist with correct permissions.

# Create base custom directory
sudo mkdir -p /var/lib/gitea/custom
sudo chown git:git /var/lib/gitea/custom
sudo chmod 755 /var/lib/gitea/custom

# Create directories for public assets and template overrides
sudo mkdir -p /var/lib/gitea/custom/public/img
sudo mkdir -p /var/lib/gitea/custom/templates/custom
sudo chown -R git:git /var/lib/gitea/custom/public /var/lib/gitea/custom/templates
sudo chmod -R 755 /var/lib/gitea/custom/public /var/lib/gitea/custom/templates

2. Add Custom Logo:

  • Copy your logo file (e.g., my-logo.png) to the Gitea server.
  • Move it into the custom image directory and name it appropriately (Gitea often looks for logo.png by default).

    # Example: Assuming your logo my-logo.png is in the current user's home dir
    sudo cp ~/my-logo.png /var/lib/gitea/custom/public/img/logo.png
    # Ensure permissions are correct
    sudo chown git:git /var/lib/gitea/custom/public/img/logo.png
    sudo chmod 644 /var/lib/gitea/custom/public/img/logo.png
    
    (If using SVG, use logo.svg instead).

3. Add Custom Footer Content: Create a custom template file specifically for adding content to the footer.

# Create the custom footer template file
sudo nano /var/lib/gitea/custom/templates/custom/footer.tmpl
  • Paste the following HTML content (or your own):

    <div class="ui container" style="text-align: center; margin-top: 1em; padding-bottom: 1em; border-top: 1px solid #ddd;">
        <p>Powered by Gitea. Hosted by <strong>My Awesome University Department</strong> &copy; {{Year}}</p>
        <p><a href="/terms-of-service">Terms of Service</a> | <a href="/privacy-policy">Privacy Policy</a></p>
    </div>
    

    • {{Year}} is a Go template variable provided by Gitea that will render the current year.
    • You can add any valid HTML here.
  • Save the file (Ctrl+X, Y, Enter in nano).

  • Set correct ownership:
    sudo chown git:git /var/lib/gitea/custom/templates/custom/footer.tmpl
    sudo chmod 644 /var/lib/gitea/custom/templates/custom/footer.tmpl
    

4. Restart Gitea: Apply the changes by restarting the service.

sudo systemctl restart gitea

5. Verify Changes:

  • Open your Gitea instance in your browser. You might need to do a hard refresh (Ctrl+Shift+R or Cmd+Shift+R) or clear your browser cache to see the changes.
  • Check the Logo: Look at the top-left corner (or wherever the theme places the main logo). Your custom logo should appear instead of the default Gitea logo.
  • Check the Footer: Scroll down to the bottom of any page. You should see the custom text you added in footer.tmpl below the standard Gitea footer links.

You have successfully customized the logo and added content to the footer, demonstrating how to modify Gitea's appearance using the custom directory.

Understanding and Modifying app.ini

The /etc/gitea/app.ini file is the heart of Gitea's configuration. While the web installer and admin UI configure many settings, manually editing app.ini provides access to all possible options and finer control.

Structure:

  • INI file format (sections denoted by [section_name], key-value pairs KEY = value).
  • Comments start with # or ;.
  • Values are typically strings, booleans (true, false), integers, or durations (e.g., 1h, 30m, 10s). String values generally don't need quotes unless they contain special characters.

Key Sections (Non-Exhaustive):

  • Overall (DEFAULT block / top level): Settings like APP_NAME, RUN_USER, RUN_MODE (prod recommended).
  • [server]: Core server settings - PROTOCOL, DOMAIN, ROOT_URL, HTTP_ADDR, HTTP_PORT, SSH_DOMAIN, SSH_PORT, LFS_START_SERVER, CERT_FILE, KEY_FILE, OFFLINE_MODE, DISABLE_ROUTER_LOG, ENABLE_PPROF.
  • [database]: Database connection details - DB_TYPE, HOST, NAME, USER, PASSWD, SSL_MODE, PATH (for SQLite3), connection pool settings.
  • [indexer]: Configuration for code and issue indexers.
  • [session]: Session management - PROVIDER (memory, file, redis, db), PROVIDER_CONFIG (path for file, connection string for redis), COOKIE_NAME, COOKIE_SECURE, GC_INTERVAL_TIME.
  • [picture]: Avatar settings - AVATAR_UPLOAD_PATH, DISABLE_GRAVATAR, ENABLE_FEDERATED_AVATAR.
  • [attachment]: Settings for file attachments - ENABLED, PATH, MAX_SIZE, ALLOWED_TYPES.
  • [log]: Logging configuration - MODE (console, file, conn), LEVEL (Trace, Debug, Info, Warn, Error, Critical), ROOT_PATH (directory for file logs), ROUTER_LOG_MODE (web request logging).
  • [mailer]: Email notification settings - ENABLED, HOST, PORT, USER, PASSWD, FROM, IS_TLS_ENABLED, SKIP_VERIFY.
  • [cache]: Caching configuration - ENABLED, ADAPTER, INTERVAL, ITEM_TTL, HOST (for redis/memcache).
  • [service]: Controls enabled/disabled features and defaults - ACTIVE_CODE_LIVE_MINUTES, DISABLE_REGISTRATION, REQUIRE_SIGNIN_VIEW, ENABLE_NOTIFY_MAIL, ENABLE_CAPTCHA, DEFAULT_KEEP_EMAIL_PRIVATE, DEFAULT_ALLOW_CREATE_ORGANIZATION.
  • [webhook]: Webhook global settings - QUEUE_LENGTH, DELIVER_TIMEOUT, SKIP_TLS_VERIFY.
  • [oauth2]: Global OAuth2 settings. Specific providers are configured under [oauth2.providername].
  • [ui]: User interface settings - EXPLORE_PAGING_NUM, ISSUE_PAGING_NUM, DEFAULT_THEME, THEMES, CUSTOM_ASSETS (paths to custom CSS/JS if needed), SHOW_USER_EMAIL.
  • [ui.meta]: Metadata for HTML head - AUTHOR, DESCRIPTION, KEYWORDS.
  • [repository]: Repository settings - ROOT (path), DEFAULT_BRANCH, ENABLE_PUSH_CREATE_USER, ENABLE_PUSH_CREATE_ORG.
  • [repository.editor]: Settings for the web-based file editor.
  • [repository.upload]: Settings for web-based file uploads.
  • [lfs]: Git LFS settings - PATH, OBJECTS_PATH.
  • [metrics]: Prometheus metrics endpoint settings.
  • [security]: Security-related settings - INSTALL_LOCK (should be true after install), SECRET_KEY (critical, auto-generated), LOGIN_REMEMBER_DAYS, COOKIE_USERNAME, COOKIE_REMEMBER_NAME.

Best Practices:

  1. Stop Gitea: Always stop the Gitea service (sudo systemctl stop gitea) before editing app.ini.
  2. Backup: Back up app.ini before making significant changes.
  3. Documentation: Refer to the official Gitea documentation for the most up-to-date and complete list of configuration options and their meanings. The sample app.ini file in the Gitea source code is also a valuable reference: https://github.com/go-gitea/gitea/blob/main/custom/conf/app.ini.sample
  4. Restart Gitea: Start Gitea (sudo systemctl start gitea) after saving changes.
  5. Check Logs: Monitor Gitea logs immediately after restarting to catch any configuration errors (sudo journalctl -u gitea -f).
  6. Minimal Changes: Change only what you need to. Avoid uncommenting and changing settings unless you understand their impact.
  7. Secrets: Be extremely careful with secrets like SECRET_KEY, database passwords, and LDAP bind passwords within the file. Ensure file permissions (chmod 640 or stricter, owned by git:git) prevent unauthorized access.

Workshop Configuring Mailer Settings

This workshop configures Gitea to send emails (for registration confirmation, password resets, notifications) using an external SMTP provider. We'll use Gmail as an example, which requires generating an "App Password".

Prerequisites:

  • A Gmail account (or another SMTP provider like SendGrid, Mailgun, etc.).
  • For Gmail: You need to enable 2-Step Verification for your Google Account and then generate an "App Password" specifically for Gitea. Standard Gmail passwords will likely not work due to security measures.
    • Enable 2-Step Verification: Google Account -> Security -> 2-Step Verification.
    • Generate App Password: Google Account -> Security -> App passwords. Select "Mail" for the app and "Other (Custom name)" for the device, name it "Gitea", and click Generate. Copy the 16-character password.

Steps:

1. Stop Gitea:

sudo systemctl stop gitea

2. Edit app.ini:

sudo nano /etc/gitea/app.ini

3. Configure the [mailer] Section: Locate the [mailer] section (or add it if it doesn't exist). Modify it as follows, replacing placeholders with your actual Gmail/App Password details:

[mailer]
ENABLED           = true
# Use 'sendmail' for local sendmail command, or 'smtp'/'smtps'/'smtp+starttls' for SMTP
PROTOCOL          = smtps
SMTP_ADDR         = smtp.gmail.com
SMTP_PORT         = 465
# Use 'PLAIN', 'LOGIN', 'CRAM-MD5'. Usually LOGIN for Gmail.
SMTP_AUTH         = LOGIN
USER              = your_full_gmail_address@gmail.com
# IMPORTANT: Use the 16-character App Password generated from Google Account Security
PASSWD            = your_16_character_app_password
# Email address Gitea emails will be sent from
FROM              = Your Gitea Server Name <your_full_gmail_address@gmail.com>
# Optional: Set HELO hostname. Defaults to OS hostname.
# HELO_HOSTNAME     = gitea.yourdomain.com
# Optional: Set Mailer type, default is "smtp"
# MAILER_TYPE = smtp
# Optional: Skip TLS verification (INSECURE, use only for testing/debugging)
# SKIP_VERIFY = false
# Optional: Use Client Certificate
# CERT_FILE = custom/mailer/cert.pem
# KEY_FILE = custom/mailer/key.pem

Explanation:

  • ENABLED = true: Activates the mailer.
  • PROTOCOL = smtps: Use SMTP over implicit SSL/TLS (common for port 465). Use smtp+starttls for STARTTLS (often port 587).
  • SMTP_ADDR: Gmail's SMTP server address.
  • SMTP_PORT: Port for smtps. Use 587 if using smtp+starttls.
  • SMTP_AUTH: Authentication mechanism.
  • USER: Your full Gmail email address.
  • PASSWD: The App Password you generated, not your regular Gmail password.
  • FROM: The email address and optional name that will appear in the "From" field of emails sent by Gitea. Using your Gmail address here is usually required by Gmail.

  • (Alternative Providers): If using SendGrid, Mailgun, etc., consult their documentation for the correct SMTP_ADDR, SMTP_PORT, PROTOCOL, and authentication details (USER might be an API key name like apikey, and PASSWD the API key itself).

4. Save and Exit: Save the changes to app.ini (Ctrl+X, Y, Enter).

5. Restart Gitea:

sudo systemctl start gitea

6. Test Mailer Configuration:

  • Log in to Gitea as an administrator.
  • Go to Site Administration -> Configuration.
  • Scroll down to the "Mailer Configuration" section.
  • Enter an email address where you can receive test emails in the "Send Test Email" field.
  • Click "Send Test Email".

7. Verify:

  • Check the Gitea UI for a success or failure message.
  • Check the recipient email inbox (including spam folder) for the test email from Gitea.
  • If it fails, check the Gitea logs (sudo journalctl -u gitea -f) for detailed error messages related to SMTP connection or authentication. Common issues include incorrect App Password, firewall blocking outbound SMTP connections from the server, or Google blocking the login attempt (check your Google Account security events).

If the test email arrives successfully, your Gitea instance is now configured to send emails. This enables features like user email verification, password resets, and potentially issue/PR notifications via email (if configured in user settings).

4. Troubleshooting Common Issues

Even with careful setup, you might encounter issues when installing or running Gitea. This section covers common problems and how to diagnose them.

General Troubleshooting Strategy:

  1. Check Gitea Service Status: Is the service actually running?
    sudo systemctl status gitea
    
    Look for Active: active (running). If it's inactive (dead) or failed, the service didn't start or crashed.
  2. Examine Gitea Logs: This is the most crucial step. Logs contain detailed error messages.
    • Systemd Journal: sudo journalctl -u gitea -f (follow logs) or sudo journalctl -u gitea -n 200 --no-pager (show last 200 lines).
    • Gitea Log Files: Check files within /var/log/gitea (configured in app.ini's [log] section), primarily gitea.log and potentially xorm.log (for SQL errors if enabled).
    • Look for lines containing [E], ERROR, FATAL, or PANIC. The lines immediately preceding these often provide context.
  3. Check Reverse Proxy Logs (if used): If using Nginx or Apache, check their error logs for issues related to proxying requests to Gitea.
    • Nginx: /var/log/nginx/error.log, /var/log/nginx/access.log. Also check site-specific logs if configured (e.g., /var/log/nginx/gitea.yourdomain.com.error.log).
    • Apache: /var/log/apache2/error.log, /var/log/apache2/access.log (paths may vary by distribution).
  4. Verify Configuration (app.ini): Double-check paths, ports, database credentials, URLs, and permissions settings in /etc/gitea/app.ini. A typo here is a common cause of problems.
  5. Check File System Permissions: Ensure the git user owns and has appropriate read/write/execute permissions on /etc/gitea, /var/lib/gitea, /var/log/gitea, and the Gitea binary (/usr/local/bin/gitea). Use ls -l and namei -om /path/to/check to diagnose permission issues along a path.
  6. Check Network Configuration:
    • Firewall: Is the Gitea port (e.g., 3000 or 443 via proxy) allowed? (sudo ufw status, sudo firewall-cmd --list-all).
    • Port Conflicts: Is another service already using the port Gitea wants to bind to? (sudo ss -tulpn | grep ':3000').
    • DNS: If using a domain name, does it resolve correctly to the server's IP? (ping gitea.yourdomain.com, dig gitea.yourdomain.com).
    • Proxy Configuration: If using a reverse proxy, ensure proxy_pass points to the correct internal Gitea address and port (http://127.0.0.1:3000), and necessary headers (X-Forwarded-For, X-Forwarded-Proto) are set.
  7. Check System Resources: Is the server out of memory, disk space, or CPU resources? (free -h, df -h, top/htop).

Specific Issues:

  • Installation Problems:

    • Permission denied during setup/first run: Usually incorrect ownership or permissions on /etc/gitea, /var/lib/gitea, or /var/log/gitea. Ensure the git user owns these directories and has write access (e.g., sudo chown -R git:git ...).
    • Database Connection Error:
      • Check credentials (USER, PASSWD), HOST, PORT, NAME in app.ini [database].
      • Ensure the database server is running.
      • Verify the Gitea database and user exist in the DB server and the user has privileges.
      • Check network connectivity/firewall between Gitea server and DB server if they are separate hosts.
      • For SQLite3, ensure the directory containing the .db file (/var/lib/gitea/data) is writable by the git user.
    • Port Conflict / bind: address already in use: Another service is using port 3000 (or the configured HTTP_PORT). Find the conflicting service (sudo ss -tulpn | grep ':3000') and stop it, reconfigure it, or change Gitea's HTTP_PORT in app.ini. If using a proxy on 80/443, ensure Gitea itself is not trying to bind to these ports.
  • Runtime Errors / Gitea Not Accessible:

    • Service Fails to Start: Check logs (journalctl -u gitea) immediately for fatal errors (often config file parsing errors, database connection failures, or permission issues on startup).
    • 502 Bad Gateway (via Reverse Proxy): The proxy (Nginx/Apache) cannot connect to the backend Gitea service.
      • Is the Gitea service running (systemctl status gitea)?
      • Is Gitea listening on the correct address and port specified in the proxy config (http://127.0.0.1:3000)? Check Gitea logs for startup messages showing the listening address. Check HTTP_ADDR in app.ini.
      • Is a firewall on the Gitea server blocking connections from the proxy (e.g., if Gitea listens on 0.0.0.0:3000 but firewall only allows from specific IPs)? Usually solved by having Gitea listen on 127.0.0.1:3000.
    • 4xx Errors (403 Forbidden, 404 Not Found): Often related to configuration or permissions within Gitea itself, or sometimes reverse proxy path issues. Check Gitea logs and proxy logs.
    • 500 Internal Server Error: A generic error indicating a problem within Gitea. Check Gitea logs (gitea.log) for stack traces or specific error messages. This could be anything from a bug to a resource issue or database problem.
  • Performance Bottlenecks:

    • Slow Web UI / Timeouts: Monitor system resources (CPU, RAM, IO). Check database performance. Analyze Gitea metrics for long request durations or high queue lengths. Consider enabling/configuring caching (Redis/Memcached). Review performance tuning section.
    • Slow Git Operations (Clone/Push/Pull): Monitor disk I/O on the volume holding /var/lib/gitea/data/gitea-repositories. Ensure sufficient RAM. Check if Git LFS is involved (large file transfers). Large repositories naturally take longer. Check network bandwidth between client and server.
  • Push/Pull Issues:

    • SSH Access:
      • Permission denied (publickey):
        • Has the user uploaded their public key to their Gitea account settings?
        • Is the correct private key being used by the local Git client (check ssh -T git@gitea.yourdomain.com)?
        • Are permissions correct on the server's ~/.ssh/authorized_keys file if Gitea is configured to manage this file directly (less common now)? Gitea usually uses its internal database or the authorized_keys file specific to the git user (/home/git/.ssh/authorized_keys), dynamically updated. Check Gitea logs for SSH-related errors.
        • Is the system SSH daemon configured correctly (/etc/ssh/sshd_config)? Is it running? Is port 22 open?
      • Repository not found: Double-check the SSH clone URL. Ensure the user has read/write access to that specific repository in Gitea.
    • HTTP/S Access:
      • Authentication failed: Incorrect username or password/token. Generate a new token in Gitea user settings -> Applications if needed. Check if LDAP or another auth provider is involved.
      • 403 Forbidden: User authenticated successfully but lacks permission to push to the repository/branch. Check Gitea repository settings -> Collaborators or Team permissions. Check branch protection rules.
      • fatal: The remote end hung up unexpectedly / Timeout / Large File Errors: Often happens during large pushes. Increase proxy timeouts (proxy_read_timeout in Nginx) and potentially client_max_body_size. Consider using Git LFS for very large files. Check server resources (Disk IO, CPU).
    • Git LFS Issues: Ensure LFS support is enabled in app.ini ([server] LFS_START_SERVER = true). Ensure the LFS data path ([lfs] PATH) is writable by git. Check proxy configuration allows large requests.
  • Backup/Restore Failures:

    • gitea dump fails: Insufficient temporary disk space (--tempdir). Permission errors (run as git user or ensure user running dump can read all necessary files/DB). Database dump command fails (check DB credentials/connectivity).
    • Restore fails: Incorrect file permissions after restoring files (chown -R git:git). Database restore issues (wrong user/password, database not empty, version mismatch). Gitea version mismatch between backup and restore target. Missing data components (forgot to restore attachments, LFS, etc.).

Workshop Diagnosing a Failed Push

This workshop presents two common scenarios for a failed git push and how to diagnose them.

Scenario 1: SSH Key Issue (Permission denied (publickey))

1. Simulate the Problem:

  • Go to your Gitea user settings -> SSH / GPG Keys.
  • Temporarily delete the SSH public key you normally use for pushing.
  • On your local machine, try to push to a repository using the SSH URL:
    cd /path/to/your/local/repo
    echo "Test SSH fail $(date)" >> ssh_fail_test.txt
    git add .
    git commit -m "Test SSH fail"
    git push origin main # Or your branch name
    
  • Observe Error: You should receive an error similar to:
    git@gitea.yourdomain.com: Permission denied (publickey).
    fatal: Could not read from remote repository.
    Please make sure you have the correct access rights
    and the repository exists.
    

2. Diagnose:

  • Step 1: Check Gitea UI: Go back to Gitea user settings -> SSH / GPG Keys. Confirm the public key is indeed missing or incorrect. Self-correction: The key is missing because we deleted it.
  • Step 2: Verify Local Key: On your local machine, check which keys your SSH agent knows about.
    ssh-add -l
    
    Is the key you expect to use listed? If not, add it: ssh-add ~/.ssh/your_private_key. If it is listed, verify it corresponds to the public key you intended to add to Gitea. You can display the public key from your private key: ssh-keygen -y -f ~/.ssh/your_private_key.
  • Step 3: Test SSH Connection Directly: Use ssh -T to test the connection and key authentication at the SSH level, bypassing Git initially.
    ssh -Tv git@gitea.yourdomain.com
    # The -v (verbose) flag provides detailed connection debugging info
    
    Look for lines indicating which keys are offered and whether the server accepts any. Since the key is missing on the server, you'll likely see "Permission denied".
  • Step 4: Check Server SSH Logs: On the Gitea server, check the system's SSH daemon logs.
    sudo journalctl -u sshd # Or /var/log/auth.log on older Debian/Ubuntu
    
    Look for connection attempts from your IP address around the time of the failed push. It might show failed authentication attempts.
  • Step 5: Check Gitea Logs: Check Gitea's logs for potential relevant messages, though the primary failure is at the SSH authentication level before Gitea's Git hooks are involved.
    sudo journalctl -u gitea -n 50
    

3. Fix:

  • Re-add the correct public SSH key (~/.ssh/your_public_key.pub) to your Gitea account settings -> SSH / GPG Keys.
  • Retry the git push. It should now succeed.

Scenario 2: HTTP Access Issue (403 Forbidden)

1. Simulate the Problem:

  • Log in to Gitea as an admin or repository owner.
  • Go to a repository's settings -> Collaborators.
  • Ensure your regular user account (the one you'll push with) is listed as a collaborator. Change its permission level to Read (instead of Write or Admin).
  • On your local machine, try to push using the HTTPS URL:

    cd /path/to/your/local/repo
    # Make sure your remote 'origin' uses the HTTPS URL
    # git remote set-url origin https://gitea.yourdomain.com/YourUser/YourRepo.git
    echo "Test HTTP 403 $(date)" >> http_403_test.txt
    git add .
    git commit -m "Test HTTP 403"
    git push origin main
    

  • You'll be prompted for your Gitea username and password (or token). Enter them correctly.

  • Observe Error: You should receive an error similar to:
    fatal: unable to access 'https://gitea.yourdomain.com/YourUser/YourRepo.git/': The requested URL returned error: 403
    

2. Diagnose:

  • Step 1: Verify Credentials: Double-check you entered the correct username and password/token. If using a token, ensure it hasn't expired and has write:repository permissions in Gitea settings -> Applications.
  • Step 2: Check Gitea UI Permissions: Go back to the repository settings -> Collaborators in Gitea. Confirm your user only has "Read" access. Self-correction: This is the cause we simulated.
  • Step 3: Check Branch Protection: Go to repository settings -> Branches. Is there a branch protection rule for main (or the branch you pushed to) that prevents direct pushes or requires specific approvals/status checks? If so, your push might be blocked even with Write access if the rule conditions aren't met. (In this specific scenario, Read access is the primary blocker).
  • Step 4: Check Reverse Proxy Logs: Examine Nginx/Apache access logs. You should see a POST request to the repository URL resulting in a 403 status code being returned from Gitea. This confirms the request reached Gitea, but Gitea denied it.
    # Example: Check Nginx access log
    sudo tail -n 50 /var/log/nginx/access.log # Or your specific site log
    # Look for lines like:
    # <Your_IP> - YourUser [Date] "POST /YourUser/YourRepo.git/git-receive-pack HTTP/1.1" 403 ...
    
    Check the Nginx error.log as well, though a 403 is usually an application-level denial, not a proxy error.
  • Step 5: Check Gitea Logs: Check /var/log/gitea/gitea.log or journalctl -u gitea. Look for log entries corresponding to your push attempt. Gitea should log the access denial, potentially mentioning the user and repository involved and the reason (insufficient permissions).

3. Fix:

  • In the Gitea repository settings -> Collaborators, change the permission for your user back to Write or Admin.
  • Retry the git push. It should now succeed (assuming no branch protection rules interfere).

These workshops illustrate a systematic approach: identify the exact error message, check the most likely configuration areas (keys/permissions), verify connectivity/authentication, and then consult logs (proxy and application) for specific details.

5. CI/CD Integration with External Tools

Continuous Integration (CI) and Continuous Deployment/Delivery (CD) are practices that automate the building, testing, and deployment of software, triggered by code changes. While Gitea offers its own CI/CD solution (Gitea Actions, covered next), it also integrates seamlessly with numerous external CI/CD tools using its powerful Webhook system. This allows you to leverage existing CI/CD infrastructure or choose specialized tools.

Introduction to CI/CD Principles

  • Continuous Integration (CI): The practice of frequently merging developers' code changes into a central repository, after which automated builds and tests are run. The main goals are to detect integration errors quickly, improve code quality, and reduce the time it takes to validate and release new software updates.
  • Continuous Delivery (CD): An extension of CI where code changes, after passing automated tests, are automatically released to a staging or pre-production environment. Manual approval is often required before deploying to the final production environment.
  • Continuous Deployment (CD): Goes one step further than Continuous Delivery by automatically deploying every change that passes all stages of the production pipeline to the production environment.

The Role of Gitea Webhooks

Webhooks are the primary mechanism for integrating Gitea with external CI/CD systems. The workflow typically looks like this:

  1. Event Occurs: A developer pushes code to a Gitea repository, creates a pull request, or merges a branch.
  2. Webhook Trigger: Gitea detects the event (e.g., push).
  3. HTTP POST Request: Gitea sends an HTTP POST request containing a JSON payload with details about the event (repository, commit hash, pusher, etc.) to a predefined URL (the webhook receiver endpoint of the CI/CD tool). A shared secret can be used to sign the payload for verification.
  4. CI/CD Pipeline Execution: The external CI/CD tool receives the webhook, verifies it (optional), parses the payload, and triggers the corresponding build/test/deploy pipeline. This pipeline usually involves:
    • Checking out the specific commit from the Gitea repository.
    • Running build commands (compiling code, creating artifacts).
    • Running automated tests (unit, integration, etc.).
    • (Optionally) Deploying the application to a target environment.
    • (Optionally) Sending status updates back to Gitea (e.g., marking the commit as success/failure).

Overview of External CI/CD Tools

Several popular tools integrate well with Gitea:

  • Jenkins: A highly extensible, open-source automation server. Widely used, mature, with a vast plugin ecosystem. Integration often uses the Gitea plugin for Jenkins or generic webhook triggers. Can be complex to set up and manage.
  • Drone CI: A modern, container-native CI/CD platform. Pipelines are defined in a YAML file (.drone.yml) within the repository and executed in Docker containers. Known for simplicity and tight Docker integration. Requires setting up a Drone server and runners.
  • Woodpecker CI: A community fork of Drone CI, also container-native with YAML pipelines (.woodpecker.yml). Offers a similar experience to Drone with a focus on open-source community development.
  • GitLab Runner: While primarily designed for GitLab CI, the GitLab Runner can be configured to work with other Git sources like Gitea using the custom executor or by manually configuring webhook triggers and scripts. This allows reusing existing GitLab Runner infrastructure.

Jenkins Integration Example

Jenkins integration typically involves:

  1. Install Gitea Plugin: In Jenkins -> Manage Jenkins -> Plugins -> Available plugins, search for and install the "Gitea" plugin.
  2. Configure Gitea Server: In Jenkins -> Manage Jenkins -> Configure System, find the "Gitea Servers" section. Add your Gitea server URL. Add credentials (e.g., a Gitea Personal Access Token) for Jenkins to interact with Gitea (checkout code, update status). Test the connection.
  3. Create Jenkins Job: Create a Pipeline job (or other types).
  4. Configure Trigger: In the job configuration, under "Build Triggers", select "Gitea webhook trigger on Gitea Server". Configure which events should trigger the build (Push, Pull Request, Tag).
  5. Configure Pipeline: Define your pipeline script (declarative or scripted). Use the checkout scm step, which, thanks to the plugin and server configuration, will know how to check out code from the correct Gitea repository and commit.
  6. Configure Gitea Webhook: In your Gitea repository -> Settings -> Webhooks, add a new webhook pointing to https://your-jenkins-url/gitea-webhook/post. Select the trigger events. Use a secret if desired and configure it in the Jenkins Gitea server settings.

Drone CI / Woodpecker CI Integration Example

These tools often use a tighter integration:

  1. Register Gitea Application: In Gitea -> Settings -> Applications, create an OAuth2 application for Drone/Woodpecker. Note the Client ID and Client Secret.
  2. Configure CI/CD Server: Configure the Drone/Woodpecker server with the Gitea server URL and the OAuth2 credentials.
  3. User Login/Activation: Users typically log into the Drone/Woodpecker UI using their Gitea account (via OAuth2). They can then activate repositories they have access to.
  4. Webhook Auto-Configuration: Activating a repository usually automatically creates the necessary webhook in Gitea.
  5. Define Pipeline: Create a .drone.yml or .woodpecker.yml file in the root of your Gitea repository defining the pipeline steps, which run inside Docker containers.

Security Considerations

  • Webhook Secrets: Always configure a secret token for your webhooks in Gitea and verify the payload signature (X-Gitea-Signature) in your receiving CI/CD tool. This prevents attackers from triggering your pipelines with fake requests.
  • Network Access: Ensure your CI/CD tool can reach your Gitea server (for checking out code, API calls) and that Gitea can reach your CI/CD tool's webhook endpoint. Use firewalls to restrict access appropriately.
  • Authentication Tokens: Protect Gitea Personal Access Tokens used by CI/CD tools. Grant them only the necessary scopes (e.g., read/write repository access, status updates).

Workshop Setting up Jenkins Integration via Webhook

This workshop demonstrates setting up a basic Jenkins pipeline job triggered by pushes to a Gitea repository using webhooks and the Jenkins Gitea plugin.

Prerequisites:

  • A running Gitea instance (accessible from Jenkins).
  • A running Jenkins instance (accessible from Gitea). Setting up Jenkins is beyond this scope; assume you have one running.
  • A Gitea repository (e.g., my-first-repo).
  • Admin access to both Gitea and Jenkins.

Steps:

1. Install Jenkins Gitea Plugin:

  • Log in to Jenkins.
  • Go to Manage Jenkins -> Plugins -> Available plugins.
  • Search for Gitea.
  • Select the "Gitea" plugin checkbox.
  • Click "Install without restart" (or similar). Wait for installation to complete.

2. Generate Gitea Personal Access Token (PAT):

  • Log in to Gitea as the user Jenkins will interact as (or an admin).
  • Go to Settings -> Applications.
  • Under "Generate New Token", enter a Token Name (e.g., jenkins-integration).
  • Select permissions: minimally read:repository and write:repository (for checkout) and write:issue write:notification write:pullrequest (for potential status updates, depending on plugin usage). Granting admin:repo might be simpler for testing but less secure. Review necessary permissions based on your pipeline needs.
  • Click "Generate Token".
  • Copy the generated token immediately.

3. Configure Gitea Server in Jenkins:

  • In Jenkins, go to Manage Jenkins -> Credentials -> System -> Global credentials (unrestricted).
  • Click "Add Credentials":
    • Kind: Username with password.
    • Scope: Global.
    • Username: Enter your Gitea username.
    • Password: Paste the Gitea Personal Access Token you just generated.
    • ID: Give it a recognizable ID (e.g., gitea-pat-credentials).
    • Description: (Optional) Gitea Personal Access Token.
    • Click OK.
  • Go to Manage Jenkins -> Configure System.
  • Scroll down to the "Gitea Servers" section.
  • Click "Add Gitea Server":
    • Server URL: Enter the ROOT_URL of your Gitea instance (e.g., https://gitea.yourdomain.com).
    • Manage Hooks: Check this box to allow Jenkins to manage hooks (optional, but convenient).
    • Credentials: Select the credential you just created (gitea-pat-credentials or similar based on your description/ID).
    • Click "Test Connection". You should see "Credentials verified for user ".
    • Click "Save".

4. Create a Jenkins Pipeline Job:

  • Go to Jenkins dashboard -> New Item.
  • Enter an item name (e.g., gitea-test-pipeline).
  • Select "Pipeline".
  • Click OK.

5. Configure the Job Trigger:

  • In the job configuration page, go to the "Build Triggers" section.
  • Check "Gitea webhook trigger on Gitea server".
  • Select the events you want to trigger on (e.g., keep default "Push Events").

6. Configure the Pipeline Script:

  • Scroll down to the "Pipeline" section.
  • Definition: Choose Pipeline script.
  • Enter a simple pipeline script in the Script text area:
    pipeline {
        agent any // Run on any available Jenkins agent
    
        stages {
            stage('Checkout') {
                steps {
                    echo 'Checking out code from Gitea...'
                    // This step uses the configured Gitea server and credentials
                    checkout scm
                }
            }
            stage('Build & Test') {
                steps {
                    echo 'Running build/test steps...'
                    // Replace with your actual build commands
                    sh 'ls -la' // Example: list files
                    sh 'echo Success!'
                }
            }
        }
    
        post {
            // Example: Update Gitea commit status (requires appropriate PAT permissions)
            success {
                echo 'Pipeline succeeded. Updating Gitea commit status.'
                // giteaCommitStatus context: 'Jenkins CI', state: 'success' // Uncomment if PAT has status permissions
            }
            failure {
                echo 'Pipeline failed. Updating Gitea commit status.'
                // giteaCommitStatus context: 'Jenkins CI', state: 'failure' // Uncomment if PAT has status permissions
            }
        }
    }
    
  • Click "Save".

7. Configure Webhook in Gitea:

  • Go to your Gitea repository -> Settings -> Webhooks.
  • Click "Add Webhook" -> "Gitea" (or Jenkins if available).
  • Target URL: Enter https://YOUR_JENKINS_URL/gitea-webhook/post (replace with your actual Jenkins URL).
  • POST Content Type: application/json.
  • Secret: (Optional) Enter a strong secret. If you do, go back to Jenkins -> Manage Jenkins -> Configure System -> Gitea Servers -> Advanced -> Manage hooks -> Add -> Secret text credential, add the secret, and select it here. For now, leave blank for simplicity.
  • Trigger On: Ensure "Push Events" is selected.
  • Active: Ensure checked.
  • Click "Add Webhook".
  • You can click "Test Delivery". Check the response in Gitea (should be 200 OK) and see if a build starts in Jenkins (it might trigger on the test payload depending on Jenkins plugin version).

8. Test the Integration:

  • On your local machine, clone the Gitea repository (if needed).
  • Make a change, commit, and push:
    cd /path/to/your/local/repo
    echo "Trigger Jenkins $(date)" >> jenkins_trigger.txt
    git add .
    git commit -m "Trigger Jenkins pipeline"
    git push origin main
    
  • Go to your Jenkins job page (gitea-test-pipeline). You should see a build start automatically within a few seconds.
  • Click on the build number to view its console output and see the stages execute.

You have now integrated Gitea with Jenkins, enabling automated pipeline runs on code pushes.

6. Gitea Actions (Built-in CI/CD)

Gitea Actions provides an integrated Continuous Integration/Continuous Deployment (CI/CD) system directly within Gitea. It is heavily inspired by and largely compatible with GitHub Actions, allowing you to reuse many existing workflows. Instead of relying on external tools, Gitea Actions lets you define workflows in your repository that are executed by dedicated runners managed by you.

Enabling Gitea Actions

Gitea Actions is not enabled by default. You need to enable it in your app.ini configuration file.

  1. Stop Gitea: sudo systemctl stop gitea
  2. Edit app.ini: sudo nano /etc/gitea/app.ini
  3. Add/Enable Section: Add or uncomment and set ENABLED to true in the [actions] section:
    [actions]
    ENABLED = true
    # Default Git URL for actions, can be customized if runners need a different URL
    # DEFAULT_ACTIONS_URL = https://github.com
    
  4. Save and Restart: Save the file and restart Gitea: sudo systemctl start gitea

After restarting, you should see an "Actions" tab appear in your repositories.

Understanding Runners

Workflows defined in your repositories need compute resources to execute. This is provided by runners. Gitea Actions uses a separate runner application called act_runner.

  • act_runner: A standalone binary (similar to GitLab Runner or GitHub Actions self-hosted runner) that registers itself with your Gitea instance and polls for available jobs. When a job is assigned, the runner executes the steps defined in the workflow YAML file, typically inside Docker containers (though host execution is also possible).
  • Runner Scope: Runners can be registered at different levels:
    • Instance Runners: Available to all repositories and organizations on the Gitea instance. Registered by the Gitea administrator.
    • Organization Runners: Available only to repositories within a specific organization. Registered by organization owners.
    • Repository Runners: Dedicated to a single repository. Registered by users with admin rights to the repository.
  • Labels: Runners can be assigned labels (e.g., linux, docker, gpu, arm64). Workflows can then specify runs-on: [label1, label2] to ensure jobs run only on runners with matching labels. This allows directing jobs to specific environments or hardware.

Setting up act_runner

You need to download, register, and run act_runner on a machine that can execute the jobs (and typically run Docker).

1. Download act_runner:

  • Go to the act_runner releases page: https://gitea.com/gitea/act_runner/releases
  • Download the binary appropriate for the runner machine's OS and architecture (e.g., act_runner-0.2.6-linux-amd64).
  • Make it executable and place it somewhere convenient (e.g., /usr/local/bin/act_runner).
    # Example download (replace version/arch as needed)
    wget https://dl.gitea.com/act_runner/0.2.6/act_runner-0.2.6-linux-amd64
    chmod +x act_runner-0.2.6-linux-amd64
    sudo mv act_runner-0.2.6-linux-amd64 /usr/local/bin/act_runner
    act_runner --version # Verify
    

2. Obtain a Runner Registration Token:

  • In Gitea, navigate to where you want to register the runner:
    • Instance: Site Administration -> Actions -> Runners -> Create new instance runner.
    • Organization: Organization Settings -> Actions -> Runners -> Create new organization runner.
    • Repository: Repository Settings -> Actions -> Runners -> Create new repository runner.
  • Copy the Registration Token displayed. This token is used only once for registration.

3. Register the Runner:

  • On the runner machine, run the registration command:
    act_runner register \
        --no-interactive \
        --instance https://gitea.yourdomain.com \
        --token YOUR_REGISTRATION_TOKEN \
        --name my-linux-docker-runner \
        --labels linux-docker:docker://node:18-bullseye,host-linux:host://linux/amd64
        # Comma-separated list of labels
        # Format: label-name:schema://image-or-details
        # docker://... defines the default container image if a job doesn't specify one
        # host://... allows running directly on the host
    
    • --instance: Your Gitea instance ROOT_URL.
    • --token: The registration token copied from Gitea.
    • --name: A descriptive name for the runner.
    • --labels: Define labels and their associated execution environments. docker://node:18-bullseye means jobs using the linux-docker label will run inside a node:18-bullseye container by default. host://linux/amd64 defines a label for running directly on the host (use with caution).
  • This command creates a .runner configuration file in the current directory (or ~/.act-runner/) containing the runner's details and a new authentication token for communicating with Gitea.

4. Run the Runner:

  • Directly:
    act_runner daemon
    
    This starts the runner in the foreground. It will poll Gitea for jobs.
  • As a Systemd Service (Recommended): Create a service file to manage the runner.
    sudo nano /etc/systemd/system/gitea-runner.service
    
    Paste configuration (adjust User, Group, WorkingDirectory, ExecStart path):
    [Unit]
    Description=Gitea Actions Runner (act_runner)
    After=network.target docker.service # Add docker.service if jobs use Docker
    Requires=docker.service # Make Docker required if jobs need it
    
    [Service]
    User=your_runner_user # A dedicated user or your own user
    Group=your_runner_group # Often 'docker' group if using Docker socket
    WorkingDirectory=/home/your_runner_user/ # Directory containing the .runner file
    ExecStart=/usr/local/bin/act_runner daemon
    Restart=always
    RestartSec=10
    
    [Install]
    WantedBy=multi-user.target
    
    Note: The runner user needs permissions to interact with Docker (usually by being in the docker group: sudo usermod -aG docker your_runner_user) if executing Docker-based jobs. The WorkingDirectory must contain the .runner file created during registration.
    • Enable and start the service:
      sudo systemctl daemon-reload
      sudo systemctl enable gitea-runner
      sudo systemctl start gitea-runner
      sudo systemctl status gitea-runner # Verify it's running
      

5. Verify Runner in Gitea:

  • Go back to the Runners page in Gitea (Instance/Org/Repo). You should see your newly registered runner listed with a green checkmark indicating it's active and polling.

Workflow Syntax

Gitea Actions workflows are defined in YAML files located in the .gitea/workflows/ directory of your repository (note the .gitea path, unlike GitHub's .github).

  • File Path: .gitea/workflows/your_workflow_name.yaml (or .yml)
  • Basic Structure:

    name: My First Workflow # Name displayed in Gitea UI
    
    # Controls when the workflow runs
    on:
      push: # Trigger on push events
        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
    
    # Defines one or more jobs that run in parallel by default
    jobs:
      build: # Name of the job
        runs-on: linux-docker # Specify runner label(s) - MUST match a registered runner label
        steps:
          # Step 1: Check out the repository code
          - name: Checkout code
            uses: actions/checkout@v3 # Use a standard action
    
          # Step 2: Run a shell command
          - name: Run a simple command
            run: echo "Hello from Gitea Actions!"
    
          # Step 3: Run multiple commands
          - name: Run build steps
            run: | # Use '|' for multi-line scripts
              echo "Building the project..."
              # Add your build commands here (e.g., make, npm install, etc.)
              echo "Build complete."
    
      test: # Another job, runs in parallel with 'build'
        runs-on: host-linux # Run this job directly on the host runner (if configured)
        needs: build # Optional: Make this job depend on 'build' completing successfully
        steps:
          - name: Run tests
            run: echo "Running tests..." # Add your test commands
    

  • Key Elements:

    • name: Workflow name.
    • on: Event(s) that trigger the workflow (push, pull_request, schedule, workflow_dispatch). Can filter by branches, tags, paths.
    • jobs: Contains one or more jobs.
    • jobs.<job_id>: Defines a job.
    • runs-on: Specifies the label(s) of the runner required to execute the job. This is crucial.
    • steps: A sequence of tasks executed within a job.
    • steps.name: Descriptive name for a step.
    • steps.uses: Specifies an action to run (reusable unit of code). actions/checkout@v3 is essential for accessing repository code. Gitea Actions supports many GitHub Actions.
    • steps.run: Executes shell commands.
    • needs: Defines job dependencies.
  • Secrets: You can store sensitive data (API keys, passwords) as encrypted secrets in Gitea (Instance/Org/Repo Settings -> Secrets) and access them in workflows using ${{ secrets.YOUR_SECRET_NAME }}.

Viewing Action Logs

When a workflow is triggered, you can monitor its progress and view logs in the "Actions" tab of your Gitea repository. Click on a specific workflow run to see the status of each job and the detailed logs for each step.

Comparison with External CI/CD

  • Gitea Actions:
    • Pros: Tightly integrated UI, potentially simpler setup for basic needs, leverages GitHub Actions compatibility, managed within Gitea.
    • Cons: Newer compared to Jenkins/GitLab CI, runner management required, ecosystem might be smaller than Jenkins plugins.
  • External Tools (Jenkins, Drone, etc.):
    • Pros: Mature platforms, potentially more features/plugins (Jenkins), separate infrastructure might be more scalable or fit existing setups.
    • Cons: Less integrated UI experience, requires managing a separate CI/CD server, configuration via webhooks.

The choice depends on your team's needs, existing infrastructure, and desired level of integration.

Workshop Creating a Simple Gitea Actions Workflow

This workshop guides you through creating a basic workflow that triggers on push, checks out the code, and runs a simple echo command using act_runner.

Prerequisites:

  • Gitea instance with Actions enabled ([actions] ENABLED = true in app.ini).
  • An act_runner downloaded, registered (e.g., with label linux-docker), and running (act_runner daemon or via systemd). Ensure the runner is active in the Gitea UI (Site Admin/Org/Repo -> Actions -> Runners).
  • A Gitea repository to work with.

Steps:

1. Create the Workflow Directory:

  • Clone your Gitea repository locally.
  • Inside the repository, create the necessary directories:
    mkdir -p .gitea/workflows
    cd .gitea/workflows
    

2. Create the Workflow File:

  • Create a YAML file named basic-workflow.yaml (or .yml) inside .gitea/workflows/:
    nano basic-workflow.yaml
    
  • Paste the following content:

    name: Basic Gitea Workflow
    
    on:
      push: # Trigger on every push to any branch
        branches:
          - '*' # You can restrict this, e.g., [ main ]
    
    jobs:
      greet:
        # Use the label you registered your runner with
        runs-on: linux-docker
        steps:
          - name: Checkout code
            uses: actions/checkout@v3
    
          - name: Say Hello
            run: |
              echo "Hello from Gitea Actions!"
              echo "Workflow triggered by commit ${{ gitea.sha }}"
              echo "Running on branch ${{ gitea.ref }}"
              echo "Files in repository:"
              ls -la
    

    • Important: Replace linux-docker in runs-on: with the actual label you assigned to your running act_runner during registration.
    • gitea.sha and gitea.ref are context variables providing information about the event trigger.
  • Save the file (Ctrl+X, Y, Enter).

3. Commit and Push the Workflow:

  • Stage, commit, and push the new workflow file to your Gitea repository:
    cd ../.. # Go back to the repository root
    git add .gitea/workflows/basic-workflow.yaml
    git commit -m "Add basic Gitea Actions workflow"
    git push origin main # Or the branch you are working on
    

4. Observe the Workflow Run:

  • Go to your repository in the Gitea web UI.
  • Click on the "Actions" tab.
  • You should see your "Basic Gitea Workflow" listed. It should trigger automatically due to the push. It might initially show as "queued" or "in progress".
  • Click on the workflow run name (which usually includes the commit message).
  • You'll see the greet job. Click on it.
  • You can now see the logs for each step ("Checkout code", "Say Hello"). Expand the steps to view the output, including the "Hello" message and the file listing (ls -la).

5. Troubleshoot (If Necessary):

  • Workflow not triggering: Ensure Actions are enabled in app.ini. Check the on: trigger conditions in your YAML file. Ensure the .gitea/workflows/ path is correct.
  • Job stuck in "queued": Ensure your act_runner is running and connected to Gitea (check Gitea UI -> Actions -> Runners). Verify the runs-on: label in your workflow matches the label of an active runner.
  • Job fails: Examine the logs for the failing step in the Gitea Actions UI to understand the error. It could be a command error, checkout failure, or runner environment issue.

Congratulations! You've set up Gitea Actions, registered a runner, and created your first automated workflow triggered by code pushes.

7. Using the Gitea API

Gitea provides a comprehensive RESTful API (Representational State Transfer Application Programming Interface) that allows you to interact with and manage your Gitea instance programmatically. This opens up possibilities for automation, custom integrations, scripting administrative tasks, and building tools that leverage Gitea's data.

Introduction to REST APIs

A REST API defines a set of rules and conventions for how applications can communicate over a network (usually HTTP). Key concepts include:

  • Resources: Everything in Gitea (users, repositories, issues, organizations) can be considered a resource, identified by a unique URL (Uniform Resource Locator), also called an endpoint.
  • HTTP Methods: Standard HTTP verbs are used to perform actions on resources:
    • GET: Retrieve information about a resource (e.g., get user details, list repositories).
    • POST: Create a new resource (e.g., create a new repository, create an issue).
    • PATCH / PUT: Update an existing resource (e.g., edit repository settings, update an issue). PATCH typically updates only specified fields, while PUT often replaces the entire resource. Gitea primarily uses PATCH.
    • DELETE: Remove a resource (e.g., delete a repository, delete a user).
  • Request Headers: Provide metadata about the request (e.g., authentication credentials, desired response format). Common headers for Gitea API are Authorization and Content-Type.
  • Request Body: Contains data sent to the server, typically in JSON format for POST and PATCH requests.
  • Response Status Codes: Standard HTTP status codes indicate the outcome (e.g., 200 OK, 201 Created, 204 No Content, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 500 Internal Server Error).
  • Response Body: Contains the data requested (for GET) or information about the operation's result, usually in JSON format.

Gitea API Overview

  • Capabilities: The API covers most actions you can perform through the web UI, including managing users, organizations, repositories, issues, pull requests, labels, milestones, webhooks, settings, and more.
  • Documentation: Gitea provides interactive API documentation via Swagger UI. You can usually access it at https://gitea.yourdomain.com/api/swagger (replace with your Gitea ROOT_URL). This interface allows you to browse all available endpoints, view required parameters and expected responses, and even try out API calls directly from your browser (after authenticating).
  • API Base Path: API endpoints are typically prefixed with /api/v1. For example, the endpoint to get information about a user is /api/v1/users/{username}.

Authentication Methods

API requests need to be authenticated to verify the identity and permissions of the caller.

  • Access Tokens (Recommended): This is the preferred method for scripts and applications.
    • Personal Access Tokens (PATs): Users can generate tokens in their Gitea settings (Settings -> Applications -> Generate New Token). You can assign specific scopes (permissions) to tokens, limiting what actions they can perform (e.g., read:repository, write:repository, write:issue, admin:org). This adheres to the principle of least privilege.
    • Usage: Include the token in the Authorization header of your HTTP request:
      Authorization: token YOUR_ACCESS_TOKEN
      
      or sometimes as a query parameter (less secure): ?token=YOUR_ACCESS_TOKEN or ?access_token=YOUR_ACCESS_TOKEN. Using the header is strongly recommended.
  • Application Tokens (OAuth2): More complex, suitable for third-party applications integrating with Gitea on behalf of users. Involves registering an OAuth2 application in Gitea and implementing the OAuth2 flow.
  • HTTP Basic Authentication: Sending username and password directly in the Authorization header. Generally discouraged for API access due to security risks, especially over plain HTTP. Might require enabling specific settings in app.ini.

Using curl to Interact with the API

curl is a versatile command-line tool for making HTTP requests, making it ideal for testing and scripting API interactions.

  • Basic GET Request:
    # Replace placeholders
    GITEA_URL="https://gitea.yourdomain.com"
    TOKEN="YOUR_ACCESS_TOKEN"
    USERNAME="someuser"
    
    curl -X GET \
         -H "Authorization: token $TOKEN" \
         "$GITEA_URL/api/v1/users/$USERNAME"
    
  • Making a POST Request with JSON Data:

    # Example: Creating a repository for the authenticated user
    curl -X POST \
         -H "Authorization: token $TOKEN" \
         -H "Content-Type: application/json" \
         -d '{
               "name": "my-new-api-repo",
               "description": "Repository created via API",
               "private": false
             }' \
         "$GITEA_URL/api/v1/user/repos"
    

    • -X POST: Specifies the HTTP method.
    • -H "Header: Value": Sets request headers. Content-Type: application/json is crucial when sending JSON data.
    • -d '{"key": "value"}': Provides the request body data (the JSON payload).

Example API Calls

(Remember to replace placeholders like $GITEA_URL, $TOKEN, $OWNER, $REPO, etc.)

  • Get Authenticated User Info:
    curl -H "Authorization: token $TOKEN" "$GITEA_URL/api/v1/user"
    
  • List Repositories for User 'testuser':
    curl -H "Authorization: token $TOKEN" "$GITEA_URL/api/v1/users/testuser/repos"
    
  • List Issues in 'myorg/coolproject':
    curl -H "Authorization: token $TOKEN" "$GITEA_URL/api/v1/repos/myorg/coolproject/issues"
    
  • Create an Issue:
    curl -X POST \
         -H "Authorization: token $TOKEN" \
         -H "Content-Type: application/json" \
         -d '{
               "title": "API Test Issue",
               "body": "This issue was created using the Gitea API and curl."
             }' \
         "$GITEA_URL/api/v1/repos/myorg/coolproject/issues"
    

Parsing JSON Responses

API responses are typically in JSON format. The command-line tool jq is excellent for parsing and manipulating JSON data in scripts.

# Example: Get the ID and SSH URL of repositories owned by 'testuser'
curl -s -H "Authorization: token $TOKEN" "$GITEA_URL/api/v1/users/testuser/repos" | jq '.[] | {id: .id, ssh_url: .ssh_url}'

Use Cases

  • Automating repository creation for new projects or users.
  • Scripting bulk operations (e.g., adding labels to issues, managing collaborators).
  • Integrating Gitea events with custom internal tools or dashboards.
  • Generating reports about repository activity or user contributions.
  • Creating custom Git tooling.

Rate Limiting

To prevent abuse, Gitea imposes rate limits on API requests. Default limits are configurable in app.ini under the [api] section (MAX_RESPONSE_ITEMS, DEFAULT_PAGING_NUM). If you exceed limits, you'll receive 429 Too Many Requests errors. Be mindful of rate limits in your scripts, potentially adding delays between requests. API responses for lists are often paginated; check response headers or documentation for pagination controls (?page=, ?limit=).

Workshop Creating a Repository via the API using curl

This workshop guides you through creating a new Gitea repository for your user account using the API and curl.

Prerequisites:

  • A running Gitea instance.
  • curl command-line tool installed on your local machine.
  • A Gitea Personal Access Token (PAT) with write:repository scope.

Steps:

1. Generate a Personal Access Token (If Needed):

  • Log in to Gitea.
  • Go to Settings -> Applications.
  • Generate a new token named (e.g., api-repo-creation-token) with at least the write:repository permission scope checked.
  • Copy the generated token.

2. Set Environment Variables (Optional but Recommended): To avoid pasting the URL and token repeatedly, set them as environment variables in your terminal session:

export GITEA_URL="https://gitea.yourdomain.com" # Replace with your Gitea ROOT_URL
export GITEA_TOKEN="YOUR_COPIED_PAT"      # Replace with your actual token
(These variables typically last only for the current terminal session).

3. Prepare the API Request: We need to send a POST request to the /api/v1/user/repos endpoint with a JSON payload specifying the new repository's details.

4. Execute the curl Command:

curl -X POST \
     -H "Authorization: token $GITEA_TOKEN" \
     -H "Content-Type: application/json" \
     -H "Accept: application/json" \
     -d '{
           "name": "my-awesome-api-repo",
           "description": "Created automatically via the Gitea API!",
           "private": false,
           "auto_init": true,
           "gitignores": "Go",
           "license": "MIT",
           "readme": "Default"
         }' \
     "$GITEA_URL/api/v1/user/repos" \
     -w "\nHTTP Status Code: %{http_code}\n" # Optional: Print HTTP status code

Explanation of JSON payload fields:

  • name: The desired name of the new repository (required).
  • description: Optional description.
  • private: Set to true for a private repository, false for public.
  • auto_init: Set to true to initialize the repository with a README, license, and .gitignore.
  • gitignores: If auto_init is true, specifies a .gitignore template (e.g., "Go", "Python").
  • license: If auto_init is true, specifies a license template (e.g., "MIT", "Apache-2.0").
  • readme: If auto_init is true, specifies the README content (using "Default" creates a basic one).

5. Check the Response:

  • curl will print the JSON response from the Gitea API. If successful (HTTP Status Code: 201), the response will contain details about the newly created repository, including its ID, clone URLs, etc.
    {
      "id": 123, // Example ID
      "owner": { /* owner details */ },
      "name": "my-awesome-api-repo",
      "full_name": "your_username/my-awesome-api-repo",
      "description": "Created automatically via the Gitea API!",
      // ... other fields like html_url, ssh_url, clone_url ...
    }
    HTTP Status Code: 201
    
  • If you get an error (e.g., 400, 401, 403, 422 Unprocessable Entity), examine the JSON error message returned by Gitea for clues. Common causes:
    • 401 Unauthorized: Invalid or missing token.
    • 403 Forbidden: Token lacks write:repository scope.
    • 422 Unprocessable Entity: Invalid JSON payload, missing required field (name), or repository name already exists.
    • 400 Bad Request: Malformed request.

6. Verify in Gitea UI:

  • Go to your Gitea dashboard in the web browser.
  • You should see the new repository "my-awesome-api-repo" listed under your repositories, initialized with a README, MIT license, and Go .gitignore file.

You have successfully used the Gitea API with curl to automate repository creation. This demonstrates the power of the API for scripting administrative tasks.

8. Advanced Configuration Deep Dive (app.ini)

We previously introduced the app.ini file and covered common configuration sections. This chapter dives deeper into less frequently used but potentially powerful settings, allowing for finer control over Gitea's behavior and features. Remember to always stop Gitea before editing app.ini and back it up first.

Security ([security])

Beyond INSTALL_LOCK and SECRET_KEY, this section holds critical security parameters:

  • INTERNAL_TOKEN: A shared secret used for internal communication between Gitea components or potentially between Gitea instances in specific HA setups (consult specific documentation). It's like an API key for Gitea's own services. Generate a strong random string if you need to use features that rely on it (e.g., Git hooks calling back to the API).
    [security]
    INTERNAL_TOKEN = your_very_strong_internal_secret
    
  • PASSWORD_HASH_ALGO: Specifies the algorithm used for hashing user passwords. Supports various algorithms like pbkdf2, bcrypt, argon2. Changing this after users exist requires careful consideration and potentially password resets or migration steps, as old hashes won't match. argon2 or bcrypt are generally recommended modern choices if supported by your Gitea version and OS libraries. Default is often pbkdf2.
  • MIN_PASSWORD_LENGTH: Enforces a minimum length for user passwords. Default is often 6. Increasing this enhances security.
    MIN_PASSWORD_LENGTH = 12
    
  • PASSWORD_COMPLEXITY: Define password complexity requirements using regular expressions or named categories (e.g., lower,upper,digit,spec). Example: lower,upper,digit requires at least one lowercase letter, one uppercase, and one digit.
    PASSWORD_COMPLEXITY = lower,upper,digit,special
    
  • IMPORT_LOCAL_PATHS: Security Risk! Defaults to false. If set to true, allows administrators to migrate repositories by specifying local filesystem paths on the Gitea server. This can be dangerous if not properly secured, as it could allow access to arbitrary paths readable by the git user. Use with extreme caution and only in trusted environments.
  • DISABLE_GIT_HOOKS: Set to true to prevent users from editing Git Hooks (pre-receive, update, post-receive) through the repository settings UI. This can be a security measure if you don't trust repository administrators with the ability to execute arbitrary code on the server via hooks. Default is false.

OAuth2 ([oauth2], [oauth2.providername])

While basic enabling is done via the UI, app.ini offers more control:

  • [oauth2] section: Global settings like JWT_SECRET (if using JWT for OAuth2 tokens).
  • [oauth2.providername] sections: You can manually define OAuth2 providers here instead of or in addition to the UI. This might be useful for providers not listed in the UI or for version-controlled configuration. Requires specifying CLIENT_ID, CLIENT_SECRET, OPEN_ID_CONNECT_AUTO_DISCOVERY_URL (if using OpenID Connect discovery) or manual endpoints (AUTH_URL, TOKEN_URL, PROFILE_URL, EMAIL_URL). Consult Gitea documentation for specific provider examples.

Repository Features ([repository.*])

Fine-tune repository behavior:

  • [repository.pull-request]:
    • DEFAULT_MERGE_STYLE: Set the default merge method offered in the UI (merge, rebase, rebase-merge, squash). Users can often override this per merge.
    • CHECKS_CONFLICT: If true, Gitea will check for conflicts before allowing a PR merge via the UI.
  • [repository.issue]:
    • LOCK_ISSUE_AFTER_INACTIVITY: Automatically lock (make read-only) issues after a specified duration of inactivity (e.g., 90d for 90 days). 0 disables it.
  • [repository.editor]: Configure web editor settings like LINE_WRAP_EXTENSIONS, PREVIEWABLE_FILE_MODES.
  • [repository.upload]: Configure web upload settings like FILE_MAX_SIZE, ALLOWED_TYPES.

Markup and Rendering ([markup])

Control how text content (like Markdown) is rendered:

  • ENABLED_RENDERERS: Comma-separated list of enabled renderers (e.g., .markdown,.md,.rst,.asciidoc).
  • EXTERNAL_RENDERERS: Define external commands to render specific file types.
  • [markup.sanitizer.*]: Fine-grained control over the HTML sanitizer used after rendering Markdown, etc. Allows specifying allowed elements, attributes, protocols (e.g., [markup.sanitizer.a] to control <a> tags). Useful for security or allowing specific HTML usage.

Background Tasks ([task])

Adjust settings for background tasks run by Gitea's queues:

  • QUEUE_TIMEOUT_SECONDS: Timeout for tasks like updating pull requests after pushes. Increase if tasks frequently time out on a slow system.

Time Formatting ([time])

Customize default date and time formats used throughout the Gitea UI (uses Go time layout strings):

[time]
FORMAT = 2006-01-02 15:04:05 # Example: YYYY-MM-DD HH:MM:SS

Mirroring ([mirror])

Configure defaults for repository mirroring:

  • DEFAULT_INTERVAL: Default interval for pull mirrors (e.g., 8h).
  • MIN_INTERVAL: Minimum allowed interval for pull mirrors.

Package Registries ([packages])

Gitea can act as a registry for various package types (NPM, Maven, Docker, NuGet, PyPI, etc.).

  • ENABLED: Set to true to globally enable the package registry feature.
  • CHUNKED_UPLOAD_PATH: Temporary storage for chunked uploads.
  • STORAGE_TYPE, STORAGE_PATH, STORAGE_SETTINGS: Configure where package files are stored (defaults to local filesystem within the main Gitea data directory, but could potentially use object storage with correct settings).
  • Specific settings per package type might exist (consult documentation).

HTTP Proxy ([proxy])

Configure proxy settings if Gitea needs to make outgoing HTTP/S requests (e.g., for webhooks, federated avatars, package fetching) through a proxy server:

  • PROXY_ENABLED: true to enable.
  • PROXY_URL: The URL of the proxy server (e.g., http://proxy.example.com:8080).
  • PROXY_USER, PROXY_PASSWORD: Credentials if the proxy requires authentication.

Environment Variable Overrides

Gitea allows overriding app.ini settings using environment variables. This is particularly useful in containerized environments. The format is GITEA__SECTION_NAME__KEY_NAME=value.

  • Example: Override the database host: export GITEA__database__HOST=db.example.com
  • Example: Disable registration: export GITEA__service__DISABLE_REGISTRATION=true

Double underscores (__) separate the section name and the key name, all in uppercase.

Managing Configuration

For complex setups, consider:

  • Version Control: Store your app.ini (excluding sensitive secrets) in a Git repository.
  • Templating/Configuration Management: Use tools like Ansible, Chef, Puppet, or simple shell script templating (envsubst) to generate app.ini dynamically, especially for managing secrets or environment-specific settings.
  • Includes: Check Gitea's documentation or release notes to see if a mechanism for including other configuration snippets (e.g., include = /path/to/extra.ini) is supported in your version, as this can help organize large configurations.

Workshop Enabling the Packages Registry (Docker Example)

This workshop demonstrates enabling the Gitea Packages feature and configuring it to act as a simple Docker container registry.

Prerequisites:

  • A running Gitea instance (preferably accessible via HTTPS, as Docker login often requires it).
  • Docker installed on your client machine (where you will run docker login, docker push).
  • Gitea instance needs sufficient storage space for container images.

Steps:

1. Stop Gitea:

sudo systemctl stop gitea

2. Edit app.ini:

sudo nano /etc/gitea/app.ini

3. Enable and Configure Packages: Add or modify the [packages] section:

[packages]
ENABLED = true
# Keep default storage unless you have specific needs
# CHUNKED_UPLOAD_PATH = data/packages-upload
# STORAGE_TYPE        = filesystem
# STORAGE_PATH        = data/packages

Add or modify the [packages.docker] section to specifically enable the Docker registry:

[packages.docker]
ENABLED = true
# Domain to use for Docker commands. MUST match how users access Gitea's ROOT_URL domain.
# If Gitea is at https://gitea.example.com, use gitea.example.com
# If Gitea is at https://gitea.example.com:3000, use gitea.example.com:3000
DOMAIN = gitea.yourdomain.com # Replace with your Gitea domain/hostname[:port]
  • Crucial: The DOMAIN value must match the hostname (and port, if non-standard 80/443) that your Docker client will use to connect to the registry. This is typically the domain part of your Gitea ROOT_URL.

4. Save and Restart Gitea:

sudo systemctl start gitea

5. Test Docker Login (Client Machine): On your local machine where Docker is installed:

# Replace gitea.yourdomain.com with the DOMAIN value you set in app.ini
docker login gitea.yourdomain.com
  • You will be prompted for:
    • Username: Your Gitea username.
    • Password: Use a Gitea Personal Access Token (PAT) with write:package scope. Generate one in Gitea Settings -> Applications if you don't have one suitable. Do not use your regular Gitea password.
  • You should see "Login Succeeded".

6. Prepare and Tag a Docker Image:

  • Find or build a simple Docker image locally. Let's use hello-world:
    docker pull hello-world
    
  • Tag the image according to the Gitea Docker registry format: <GITEA_DOMAIN>/<GITEA_OWNER>/<IMAGE_NAME>:<TAG>
    # Replace gitea.yourdomain.com with your domain
    # Replace your_gitea_username with your Gitea username (or org name)
    docker tag hello-world gitea.yourdomain.com/your_gitea_username/hello-test:latest
    

7. Push the Docker Image:

docker push gitea.yourdomain.com/your_gitea_username/hello-test:latest

  • You should see Docker push the image layers to your Gitea instance.

8. Verify in Gitea UI:

  • Go to your Gitea instance in the browser.
  • Navigate to your user profile (or organization) page.
  • You should now see a "Packages" tab. Click on it.
  • You should see the hello-test package listed. Clicking on it will show details, including the latest tag and instructions for pulling the image.

9. Test Docker Pull (Optional):

  • Remove the local tagged images:
    docker rmi gitea.yourdomain.com/your_gitea_username/hello-test:latest
    docker rmi hello-world
    
  • Pull the image from your Gitea registry:
    docker pull gitea.yourdomain.com/your_gitea_username/hello-test:latest
    
  • You should see Docker download the image layers from your Gitea instance.

You have successfully enabled Gitea Packages and used it as a private Docker registry, showcasing an advanced configuration feature accessible via app.ini.

9. Containerization with Docker

Running Gitea inside a Docker container (or using similar tools like Podman) has become a popular deployment method. It offers several advantages over traditional binary installations:

  • Dependency Management: The Gitea image includes all necessary libraries and dependencies (like specific Git versions if needed, Go runtime). You don't need to manage these on the host system.
  • Isolation: Gitea runs in its own isolated environment, reducing conflicts with other services on the host.
  • Reproducibility: Ensures that Gitea runs consistently across different environments (development, testing, production).
  • Easier Upgrades: Upgrading Gitea often involves simply pulling a newer image version and restarting the container (while keeping persistent data).
  • Scalability: While single-node Gitea is common, containerization is a foundational step for more complex, potentially scalable deployments (though HA Gitea remains complex, as discussed earlier).

Official Gitea Docker Image

Gitea provides official Docker images hosted on Docker Hub:

  • Image Name: gitea/gitea
  • Tags: Various tags are available:
    • latest: Tracks the latest stable release.
    • Specific versions: 1.21.5, 1.20.6, etc. (Recommended for production to pin versions).
    • Version ranges: 1.21, 1 (track latest within that range).
    • -rootless: Variants designed to run as a non-root user inside the container (good practice).
    • Check the Gitea Docker Hub page for all available tags: https://hub.docker.com/r/gitea/gitea/tags

Key Concepts for Running Gitea in Docker

  1. Volumes (Data Persistence): This is the most critical aspect. Containers are ephemeral by default; if you remove the container, any data written inside it (like repositories, database file, configuration) is lost. You must use Docker volumes or bind mounts to store Gitea's data persistently on the host filesystem or in Docker-managed volumes.
    • Gitea Data Directory: The official image uses /data inside the container as the main directory for persistent data (repositories, attachments, custom files, LFS, indexers, package storage, and the default location for the SQLite DB and app.ini if not customized).
    • Mapping: You need to map a host directory or a named Docker volume to /data.
      • Bind Mount Example: -v /path/on/host/gitea:/data
      • Named Volume Example: -v gitea-data:/data (Docker manages the volume's location on the host). Named volumes are often preferred.
    • Configuration: The app.ini will be located at /data/gitea/conf/app.ini inside the container unless you change Gitea's configuration path via environment variables. You might map a specific host file to this location or edit it within the volume.
    • Database: If using SQLite, the database file resides within the /data volume. If using external PostgreSQL/MySQL, you configure connection details via environment variables or app.ini, but the DB data itself lives outside the Gitea container (ideally in its own container with its own persistent volume).
  2. Ports: You need to map ports from the host machine to the ports Gitea listens on inside the container.
    • HTTP Port: Gitea listens on port 3000 by default. Map the host port (e.g., 3000 or 80/443 if behind a host-based proxy) to container port 3000. Example: -p 3000:3000.
    • SSH Port: Gitea can run its own SSH server, typically configured to listen on port 2222 inside the container to avoid conflict with the host's SSH on port 22. You map a host port (e.g., 2222) to container port 2222. Example: -p 2222:2222. Users would then clone using ssh://git@gitea.yourdomain.com:2222/.... Alternatively, you can disable Gitea's internal SSH and continue using the host's SSH server, but this requires more complex setup involving sharing the authorized_keys file or using SSH certificates. Using the internal SSH server on a dedicated port is often simpler with Docker.
  3. Environment Variables: A convenient way to configure Gitea within Docker, overriding app.ini settings. Follow the GITEA__SECTION__KEY=value format.
    • Example: GITEA__server__ROOT_URL=https://gitea.example.com
    • Example: GITEA__database__DB_TYPE=postgres
    • Example: GITEA__database__HOST=db:5432 (where db is the hostname of the database container on the same Docker network).
    • Example: GITEA__server__SSH_PORT=2222 (Internal SSH port)
    • Example: GITEA__server__SSH_LISTEN_PORT=2222 (Port Gitea SSH binds to inside container)
  4. User/Group ID: By default, processes inside the Gitea container run as user git (UID 1000, GID 1000 in recent images). If you use bind mounts for the /data volume, the host directory you map must be readable and writable by UID 1000 / GID 1000 on the host. Alternatively, you can run the container specifying the user: --user $(id -u):$(id -g) or pass environment variables USER_UID and USER_GID (check the specific image documentation for supported methods) to match the owner of the host directory. Using named volumes often avoids these host permission issues.

Example docker run Command

This command starts a basic Gitea instance using SQLite and named volumes:

docker run -d \
    --name=gitea \
    -p 3000:3000 \
    -p 2222:2222 \
    -v gitea-data:/data \
    --restart=always \
    gitea/gitea:latest
  • -d: Run in detached mode (background).
  • --name=gitea: Assign a name to the container.
  • -p 3000:3000: Map host port 3000 to container port 3000 (HTTP).
  • -p 2222:2222: Map host port 2222 to container port 2222 (SSH).
  • -v gitea-data:/data: Create/use a named volume gitea-data and mount it to /data inside the container for persistence. Docker manages this volume.
  • --restart=always: Automatically restart the container if it stops.
  • gitea/gitea:latest: The image to use.

You would then access Gitea at http://your_docker_host_ip:3000 and configure it via the web UI or by placing/editing app.ini within the volume (/var/lib/docker/volumes/gitea-data/_data/gitea/conf/app.ini - path might vary). Remember to set SSH_PORT to 2222 and ROOT_URL correctly during setup.

docker-compose simplifies managing multi-container applications. It's ideal for running Gitea alongside a dedicated database like PostgreSQL.

Create a file named docker-compose.yml:

version: "3.8"

networks:
  gitea-net:
    external: false

volumes:
  gitea-data:
    driver: local
  postgres-data:
    driver: local

services:
  gitea:
    image: gitea/gitea:latest # Or pin to a specific version
    container_name: gitea_server
    restart: always
    networks:
      - gitea-net
    volumes:
      - gitea-data:/data
      # Optional: Mount timezone and localtime for correct time display in logs/UI
      # - /etc/timezone:/etc/timezone:ro
      # - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"   # HTTP
      - "2222:2222"   # SSH
    depends_on:
      - db # Wait for the database service to be ready (basic check)
    environment:
      # See https://docs.gitea.io/en-us/config-cheat-sheet/
      # Basic settings (adjust domain, URLs)
      - USER_UID=1000 # Use host user ID if needed for volume permissions
      - USER_GID=1000 # Use host group ID if needed
      - GITEA__server__DOMAIN=gitea.yourdomain.com
      - GITEA__server__ROOT_URL=https://gitea.yourdomain.com # Assuming HTTPS via external proxy later
      - GITEA__server__SSH_DOMAIN=gitea.yourdomain.com
      - GITEA__server__HTTP_PORT=3000
      - GITEA__server__SSH_PORT=2222 # Port clients use (maps to host port 2222)
      - GITEA__server__SSH_LISTEN_PORT=2222 # Port Gitea binds SSH to inside container
      - GITEA__server__DISABLE_SSH=false
      - GITEA__server__OFFLINE_MODE=false
      # Database Settings (PostgreSQL)
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=db:5432 # 'db' is the service name of the postgres container
      - GITEA__database__NAME=gitea
      - GITEA__database__USER=gitea
      - GITEA__database__PASSWD=a_strong_password # Use a strong password!
      - GITEA__database__SSL_MODE=disable # Or 'require' if DB connection needs SSL
      # Other recommended settings
      - GITEA__service__DISABLE_REGISTRATION=true
      - GITEA__log__LEVEL=Info
      - GITEA__security__INSTALL_LOCK=true # Set to true after initial setup

  db:
    image: postgres:15 # Or your preferred version
    container_name: gitea_db
    restart: always
    networks:
      - gitea-net
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=a_strong_password # MUST match GITEA__database__PASSWD
      - POSTGRES_DB=gitea
    ports:
      # Only expose if needed for external access/debugging (usually not needed)
      # - "5432:5432"
  • How to Use:
    1. Save the content as docker-compose.yml.
    2. Adjust GITEA__server__DOMAIN, GITEA__server__ROOT_URL, GITEA__database__PASSWD, etc.
    3. Run docker compose up -d in the same directory. This creates the network, volumes, and starts both containers.
    4. Access Gitea at http://your_docker_host_ip:3000. The first time, Gitea will perform the initial setup using the environment variables. You'll mainly need to create the admin user via the web UI.
    5. To stop: docker compose down.
    6. To view logs: docker compose logs -f gitea or docker compose logs -f db.

Upgrading with Docker

  1. Backup: Always back up your persistent volumes (gitea-data, postgres-data) before upgrading! Use standard Docker volume backup techniques or stop containers and copy data.
  2. Update Image Tag: Modify the image: tag in your docker-compose.yml (e.g., from gitea/gitea:1.21.5 to gitea/gitea:1.22.0).
  3. Pull New Image: docker compose pull gitea
  4. Recreate Container: docker compose up -d --force-recreate gitea (Docker Compose will stop the old container, create a new one using the new image but attach the existing gitea-data volume). Gitea should perform any necessary database migrations automatically on startup.
  5. Check logs for successful startup and migration messages.

Workshop Running Gitea and PostgreSQL with Docker Compose

This workshop guides you through setting up Gitea with a PostgreSQL database using the docker-compose.yml file provided above.

Prerequisites:

  • Docker and Docker Compose installed on your Linux host.
  • A domain name or IP address for your Gitea instance (for configuration).

Steps:

1. Create a Project Directory:

mkdir ~/gitea-docker
cd ~/gitea-docker

2. Create docker-compose.yml:

nano docker-compose.yml

  • Paste the docker-compose.yml content from the section above into the editor.

3. Customize Configuration:

  • Carefully review and edit the environment: variables under the gitea service:
    • GITEA__server__DOMAIN: Set to your server's IP or domain (e.g., 192.168.1.100 or gitea.example.com).
    • GITEA__server__ROOT_URL: Set to the full URL users will access (e.g., http://192.168.1.100:3000 or https://gitea.example.com if you plan to put a proxy in front later).
    • GITEA__server__SSH_DOMAIN: Match GITEA__server__DOMAIN.
    • GITEA__database__PASSWD: Change a_strong_password to a unique, strong password.
    • GITEA__service__DISABLE_REGISTRATION: Keep true unless you want public registration.
    • GITEA__security__INSTALL_LOCK: Keep true (or set later after first setup).
  • Crucially: Ensure the POSTGRES_PASSWORD under the db service exactly matches the GITEA__database__PASSWD you set for the gitea service.

4. Save the File: Save the changes (Ctrl+X, Y, Enter in nano).

5. Start the Services: Run Docker Compose in detached mode:

docker compose up -d

  • Docker will pull the gitea/gitea and postgres images (if not already present) and then create and start the containers, network, and volumes. This might take a few minutes the first time.

6. Check Container Status:

docker compose ps

  • You should see two containers (gitea_server, gitea_db) with status "running" or "healthy".

7. Monitor Logs (Optional):

Watch the logs, especially on the first run, to see Gitea initialize and connect to the database.

docker compose logs -f gitea

  • Look for messages indicating successful database connection and web server startup listening on port 3000. Press Ctrl+C to exit log following.

8. Access Gitea Web UI:

  • Open your web browser and navigate to the ROOT_URL you configured (e.g., http://your_docker_host_ip:3000).
  • You should see the Gitea "Sign In" page (or the initial install page if INSTALL_LOCK wasn't set or Gitea needs initial admin setup). Since we configured the database via environment variables, Gitea should skip the installation page.
  • Important: The very first user registered on a Gitea instance automatically becomes an administrator. If registration is disabled (DISABLE_REGISTRATION=true), you'll need to find another way to create the first admin (e.g., temporarily enable registration, register, then disable again, or use the command-line gitea admin user create - see Workshop below).

9. Create Initial Admin User (Command Line):

If registration is disabled, the easiest way to create the first admin is via the gitea command inside the running container:

# Find your Gitea container name (e.g., gitea_server) if needed: docker ps
docker exec -it gitea_server gitea admin user create --username youradmin --password 'aVeryStrongPassword' --email 'admin@example.com' --admin --must-change-password=false
  • Replace gitea_server with your container name/ID.
  • Replace youradmin, 'aVeryStrongPassword', and admin@example.com with your desired credentials.
  • --admin flag makes this user an administrator.
  • --must-change-password=false avoids forcing a password change on first login.

10. Log In and Explore:

  • Go back to the Gitea web UI and log in with the admin credentials you just created.
  • Explore the instance. Create a repository. Test cloning via HTTP (:3000) and SSH (:2222).

11. Stopping the Services: When you want to stop Gitea and the database:

docker compose down
(Your data will persist in the named volumes gitea-data and postgres-data). To start again later, just run docker compose up -d.

You now have a Gitea instance running with Docker Compose, using a persistent PostgreSQL database, showcasing a robust and common deployment pattern.

10. Security Hardening

While Gitea itself is developed with security in mind, and we've already implemented HTTPS, securing your self-hosted instance is an ongoing process involving multiple layers. This section details further steps to harden your Gitea installation against common threats.

Recap Basic Security

  • HTTPS: Ensure all access is via HTTPS (using a reverse proxy like Nginx with valid certificates, e.g., Let's Encrypt).
  • Strong Credentials: Use strong, unique passwords for the Gitea admin account, database user, etc. Use Personal Access Tokens with limited scopes for API/Git access where possible.
  • Disable Registration: Set DISABLE_REGISTRATION = true in app.ini unless you explicitly intend to run a public instance.
  • Principle of Least Privilege: Run Gitea as a dedicated non-root user (git). Ensure file permissions (/etc/gitea, /var/lib/gitea, /var/log/gitea) are restrictive (e.g., owned by git:git, modes 750 or 640 where appropriate).

Fail2ban for Brute-Force Protection

Fail2ban scans log files and bans IPs that show malicious signs (too many password failures, seeking exploits, etc.). It's effective against brute-force login attempts on both the web UI and SSH.

1. Install Fail2ban:

  • Debian/Ubuntu: sudo apt update && sudo apt install fail2ban
  • CentOS/RHEL/Fedora: sudo dnf install fail2ban
  • Enable and Start:
    sudo systemctl enable fail2ban
    sudo systemctl start fail2ban
    

2. Create Gitea Filter: Fail2ban needs to know how to identify failed login attempts in Gitea's logs. Create a filter file:

sudo nano /etc/fail2ban/filter.d/gitea.conf

Paste the following content (Adjust regex if your Gitea log format differs significantly, check /var/log/gitea/gitea.log for failed login messages):

[Definition]
# Filter for Gitea web login failures and potentially API/SSH key failures
# Adjust logpath in jail.local to point to your actual gitea.log
# Example Gitea Log Line for failed web login:
# ... [WARN] Failed authentication attempt for user 'wronguser' from <HOST>
# Example Gitea Log Line for failed SSH key: (May vary based on SSH setup)
# ... ssh: error reading username from <HOST> port XXXXX: EOF
# ... ssh: Invalid user testuser from <HOST> port XXXXX

failregex = ^.*\[W\] Failed authentication attempt for user '.*' from <HOST>$
            ^.* ssh: Invalid user .* from <HOST> port \d+$
            ^.* ssh: error reading username from <HOST> port \d+: EOF$
            # Add more patterns if needed for API failures, etc.

ignoreregex =

3. Create Gitea Jail Configuration: Create a local jail file to enable the Gitea filter and configure banning behavior. Do not edit jail.conf directly; create jail.local.

sudo nano /etc/fail2ban/jail.local

Add the following section (adjust parameters as needed):

[gitea]
enabled  = true
port     = http,https,ssh,3000,2222 # Ports Gitea is listening on (directly or via proxy/mapped ports)
filter   = gitea # Matches filter file name (gitea.conf)
logpath  = /var/log/gitea/gitea.log # Path to your Gitea log file
maxretry = 5 # Ban IP after 5 failed attempts
findtime = 10m # within a 10-minute window
bantime  = 1h # Ban for 1 hour
action   = %(action_mwl)s # Ban IP and report via email with relevant log lines (configure mail settings in jail.conf/jail.local if needed)
          # Or use %(action_)s for simple banning
  • enabled = true: Activates this jail.
  • port: Comma-separated list of ports associated with Gitea access. Fail2ban uses this for reporting and potential firewall rules. Include your SSH port (22 or 2222), HTTP/S ports (80/443 if using proxy), and direct Gitea port (3000) if accessible.
  • filter: Name of the filter definition file (without .conf).
  • logpath: Crucial! Must point to the correct Gitea log file.
  • maxretry: Number of failures before banning.
  • findtime: Time window during which maxretry failures must occur.
  • bantime: Duration of the ban (use m for minutes, h for hours, d for days, -1 for permanent).
  • action: Action to take (usually involves updating firewall rules like iptables or nftables). action_mwl includes sending an email notification.

4. Restart Fail2ban:

sudo systemctl restart fail2ban

5. Test Fail2ban (Carefully):

  • From a different IP address (e.g., mobile hotspot), try logging into the Gitea web UI multiple times (maxretry + 1) with incorrect credentials.
  • Check Fail2ban status for the Gitea jail:
    sudo fail2ban-client status gitea
    
    You should see the banned IP listed.
  • Try accessing Gitea from the banned IP – you should be blocked (connection refused/timeout).
  • To unban manually: sudo fail2ban-client set gitea unbanip YOUR_BANNED_IP

Firewall Rules

Be explicit about allowed ports:

  • Only allow ports absolutely necessary for accessing Gitea (e.g., 443 for HTTPS via proxy, 22 or your custom SSH port).
  • Block the direct Gitea port (3000) from public access if you are using a reverse proxy.
  • If possible, restrict source IPs for SSH access to only trusted networks or specific admin IPs.

Example (ufw):

# Assuming Nginx proxy on 443, Gitea SSH on 2222
sudo ufw allow https # Port 443
sudo ufw allow 2222/tcp # Custom Gitea SSH port
sudo ufw deny 3000/tcp # Block direct access
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable
sudo ufw status verbose

Regular Updates

One of the most critical security practices:

  • Gitea: Regularly check for new Gitea releases (https://github.com/go-gitea/gitea/releases). Update promptly, especially for security releases. Follow the upgrade procedure (backup, replace binary or update Docker image, restart).
  • Operating System: Keep your Linux distribution updated: sudo apt update && sudo apt upgrade -y or sudo dnf update -y.
  • Database: Update PostgreSQL/MySQL/MariaDB.
  • Reverse Proxy: Update Nginx/Apache.
  • Git: Update the Git binary (sudo apt install --only-upgrade git or sudo dnf update git).

Consider using tools like unattended-upgrades (Debian/Ubuntu) for automatic OS security patches.

SSH Security (Server-Level)

Harden the SSH daemon running on your Gitea server (used if Gitea leverages the system SSH server or for general server access):

  • Edit /etc/ssh/sshd_config:
    • PasswordAuthentication no: Strongly Recommended. Disable password logins entirely, force key-based authentication.
    • PermitRootLogin no: Prevent root login via SSH.
    • Port 22: Consider changing to a non-standard port (e.g., 2244) - minor obscurity benefit. Remember to update firewalls and client configurations if changed.
    • AllowUsers git your_admin_user: Only allow specific users to log in via SSH.
    • LogLevel VERBOSE: Provides more detail in logs for troubleshooting.
  • Restart SSH daemon after changes: sudo systemctl restart sshd.

Database Security

  • Use strong, unique passwords for the Gitea database user.
  • Configure the database user with only the necessary privileges on the Gitea database.
  • If Gitea and the database are on separate servers, configure database network access controls (e.g., PostgreSQL's pg_hba.conf, MySQL's user host restrictions, firewall rules) to only allow connections from the Gitea server's IP address.

app.ini Security Settings

  • SECRET_KEY: This is critical for signing cookies and tokens. Keep it confidential. Consider rotating it manually periodically (requires users to log in again). Gitea does not auto-rotate this.
  • REVERSE_PROXY_TRUSTED_PROXIES: If using a reverse proxy, configure this in [server] to tell Gitea which upstream proxy IPs are trusted to send headers like X-Forwarded-For. Set this to your proxy server's IP address (e.g., 127.0.0.1 if proxy is on the same machine, or the proxy's specific IP/CIDR). This prevents IP address spoofing.
    [server]
    REVERSE_PROXY_TRUSTED_PROXIES = 127.0.0.1/8, ::1/128 # Example for localhost proxy
    

Web Application Security Headers

Configure your reverse proxy (Nginx/Apache) to send security-enhancing HTTP headers:

  • Strict-Transport-Security (HSTS): Tells browsers to only connect via HTTPS.
    # Nginx Example (within server block listening on 443)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    
  • X-Frame-Options: Prevents clickjacking by controlling if the site can be embedded in an iframe.
    add_header X-Frame-Options SAMEORIGIN always;
    
  • X-Content-Type-Options: Prevents MIME-sniffing attacks.
    add_header X-Content-Type-Options nosniff always;
    
  • Content-Security-Policy (CSP): Advanced header defining allowed sources for content (scripts, styles, images). Can significantly reduce XSS risks but requires careful configuration. Start restrictive and test thoroughly. (Generating a good CSP is complex and beyond a simple example here).
  • Referrer-Policy: Controls how much referrer information is sent.
    add_header Referrer-Policy strict-origin-when-cross-origin always;
    
  • Remember to reload your reverse proxy after adding headers (sudo systemctl reload nginx).

Regular Backups and Monitoring

  • Backups: As discussed previously, secure storage and regular testing of backups are crucial for recovering from security incidents (like ransomware) as well as failures.
  • Monitoring: Regularly review Gitea logs, system logs (/var/log/auth.log, journalctl), and Fail2ban logs for suspicious activity. Consider centralized logging solutions (e.g., ELK stack, Graylog) for larger setups.

Workshop Setting up Fail2ban for Gitea Web UI Logins

This workshop focuses specifically on configuring Fail2ban to protect against brute-force attacks on the Gitea web login page.

Prerequisites:

  • Fail2ban installed and running (sudo systemctl status fail2ban).
  • Your Gitea instance is running and logging to /var/log/gitea/gitea.log (or adjust logpath accordingly).

Steps:

1. Identify Failed Login Log Pattern:

  • Try logging into your Gitea web UI with a deliberately wrong username or password.
  • Check the Gitea log file for the corresponding error message:
    sudo tail -n 20 /var/log/gitea/gitea.log | grep 'Failed authentication attempt'
    
  • Note the exact format. It likely looks similar to: YYYY/MM/DD HH:MM:SS ... [W] Failed authentication attempt for user 'baduser' from 1.2.3.4 The key parts are [W] Failed authentication attempt and the IP address at the end, represented by <HOST> in Fail2ban filters.

2. Create/Verify Gitea Filter: Ensure the Gitea filter correctly captures the failed web login attempts.

# Create or edit the filter
sudo nano /etc/fail2ban/filter.d/gitea.conf

# Ensure this line is present and matches your log format
# [Definition]
# failregex = ^.*\[W\] Failed authentication attempt for user '.*' from <HOST>$
# ... other failregex lines can exist too ...
# ignoreregex =
(If you completed the previous Fail2ban setup, this filter should already exist and contain the necessary line).

3. Create/Verify Gitea Jail Configuration: Ensure the jail configuration enables the filter and points to the correct log file.

# Create or edit the local jail config
sudo nano /etc/fail2ban/jail.local

# Ensure the [gitea] section exists and is configured
# [gitea]
# enabled  = true
# port     = http,https,ssh,3000,2222 # Adjust ports as needed
# filter   = gitea
# logpath  = /var/log/gitea/gitea.log # Verify this path!
# maxretry = 5
# findtime = 10m
# bantime  = 1h
# action   = %(action_mwl)s

4. Test the Filter Regex (Optional but Recommended): Use the fail2ban-regex tool to test your filter against your actual log file:

# Test the filter against the log file
sudo fail2ban-regex /var/log/gitea/gitea.log /etc/fail2ban/filter.d/gitea.conf

# Or test against a specific log line:
# echo "2023/10/27 10:00:00 ... [W] Failed authentication attempt for user 'test' from 1.2.3.4" | sudo fail2ban-regex - /etc/fail2ban/filter.d/gitea.conf
  • Review the output. It should show how many lines matched (Lines matched) the failregex. Ensure your failed login attempts are correctly identified.

5. Restart Fail2ban: Apply any changes made to filters or jails:

sudo systemctl restart fail2ban

6. Perform the Test Ban:

  • From a different IP address, attempt to log in to the Gitea web UI maxretry + 1 times (e.g., 6 times if maxretry = 5) using incorrect credentials within the findtime window (e.g., 10 minutes).
  • Check the Fail2ban status:
    sudo fail2ban-client status gitea
    
    Look for the "Currently banned" list – your test IP should appear there.
  • Confirm access from the banned IP is blocked.

7. Check Fail2ban Log: Review the Fail2ban log for details about the ban:

sudo tail -n 50 /var/log/fail2ban.log

  • Look for lines indicating "Ban " for the gitea jail.

Fail2ban is now actively monitoring your Gitea logs and protecting your web UI from brute-force login attacks. Remember to periodically check its status and logs.

11. Community and Contribution

Gitea is a vibrant open-source project driven by its community. Engaging with the community is beneficial for getting help, staying informed, and potentially contributing back to the project's development.

Importance of the Gitea Community

  • Support: Get help with installation, configuration, troubleshooting, and usage from developers and experienced users.
  • Knowledge Sharing: Learn best practices, discover new features, and understand different deployment scenarios.
  • Influence: Provide feedback, request features, and report bugs to help shape Gitea's future direction.
  • Collaboration: Connect with other users and developers.

Getting Help

When you encounter issues or have questions, several resources are available:

  1. Official Documentation: (docs.gitea.io) This should always be your first stop. It's comprehensive and covers installation, configuration, API, Actions, and more. Search thoroughly before asking elsewhere.
  2. Community Forum: (e.g., discourse.gitea.io) The primary platform for discussions, asking questions, sharing tips, and longer-form support. Search for existing topics before posting a new one. Provide detailed information when asking for help (Gitea version, OS, logs, configuration snippets, steps to reproduce).
  3. Chat (Discord/Matrix): For more real-time discussions, quick questions, and general chat. Links are usually available on the Gitea website (gitea.io). Useful for quick queries but less suitable for complex issues requiring detailed logs.
  4. GitHub Issues: (https://github.com/go-gitea/gitea/issues) Primarily used for reporting bugs and requesting specific features.
    • Search First: Always search existing open and closed issues thoroughly before creating a new one to avoid duplicates.
    • Bug Reports: Use the provided bug report template. Include clear steps to reproduce the issue, Gitea version, environment details, relevant logs, and expected vs. actual behavior.
    • Feature Requests: Use the feature request template. Clearly describe the proposed feature, its use case, and potential benefits.

Contributing Back

Contributing to Gitea is a great way to give back, improve the software for everyone, learn Go development (Gitea's primary language), and build your open-source portfolio.

Ways to Contribute:

  • Reporting Bugs: High-quality bug reports (as described above) are incredibly valuable.
  • Improving Documentation: If you find errors, omissions, or unclear sections in the documentation, you can submit pull requests to improve it. The documentation source is usually in the main Gitea repository or a dedicated docs repository. Look for a "Edit this page" link.
  • Testing: Test development builds or release candidates and report any issues found. Participate in testing new features.
  • Translating Gitea: Help translate the Gitea interface and documentation into other languages. Gitea often uses platforms like Weblate for collaborative translation.
  • Helping Others: Answer questions and help troubleshoot issues on the forum or chat.
  • Submitting Code (Pull Requests): This involves more technical effort:
    1. Set up Development Environment: You'll need Go, Git, Node.js/npm (for frontend), and potentially Make installed. Follow the developer setup guide in the Gitea documentation.
    2. Find an Issue: Look for issues tagged good first issue, help wanted, or find a bug you want to fix or a feature you want to implement. It's often best to comment on the issue first to discuss your approach.
    3. Fork and Branch: Fork the main Gitea repository on GitHub (or the Gitea instance where development happens, often gitea.com), clone your fork, and create a new branch for your changes.
    4. Code: Implement your fix or feature. Follow Gitea's coding style guidelines. Write tests for your changes.
    5. Test: Run linters (make lint), backend tests (make test), and frontend tests (make test-frontend) locally.
    6. Submit Pull Request: Push your branch to your fork and open a Pull Request (PR) against the main branch of the upstream Gitea repository. Fill out the PR template clearly, explaining your changes and linking to the relevant issue.
    7. Code Review: Gitea maintainers and community members will review your code, provide feedback, and request changes if necessary. Respond to feedback and update your PR accordingly.
    8. Contribution License Agreement (CLA): You might need to sign a CLA agreeing to the terms under which you contribute code.
    9. Merge: Once approved, a maintainer will merge your PR.

Gitea Governance

Gitea is a community-driven project, meaning its direction is influenced by its contributors and users, guided by a core team of maintainers. Decisions are typically made transparently through discussions on GitHub issues and pull requests.

Staying Updated

  • Releases: Watch the Gitea releases page on GitHub or the official website/blog for announcements of new versions and security updates.
  • Blog/Social Media: Follow official Gitea communication channels for news and feature highlights.

Workshop Finding and Reporting a (Hypothetical) Documentation Issue

This workshop simulates finding a minor issue in documentation and preparing to report it effectively. We won't actually submit an issue, but we'll go through the process.

Scenario: You are trying to configure the [security] section in app.ini and notice the documentation for PASSWORD_COMPLEXITY doesn't clearly list all the available named categories (like lower, upper, digit, special).

Steps:

1. Identify the Resource:

  • Navigate to the official Gitea documentation website (e.g., docs.gitea.io).
  • Find the configuration cheat sheet or the section detailing app.ini settings.
  • Locate the documentation for the [security] section and the PASSWORD_COMPLEXITY key.

2. Analyze the Documentation:

  • Read the description for PASSWORD_COMPLEXITY.
  • Assume, for this workshop, that the documentation only mentions using regular expressions but doesn't explicitly list the simpler named categories (lower, upper, etc.) as valid options, even though you know (or suspect) they work based on trying them or seeing them elsewhere.

3. Search for Existing Issues:

  • Go to the Gitea GitHub repository issues page: https://github.com/go-gitea/gitea/issues.
  • Search for existing issues related to PASSWORD_COMPLEXITY documentation. Use search terms like:
    • PASSWORD_COMPLEXITY docs
    • password complexity documentation
    • security password_complexity
    • is:issue is:open password_complexity
    • is:issue is:closed password_complexity
  • Review any relevant open or closed issues. If someone has already reported this exact documentation gap, you could potentially add a comment to that issue (e.g., "I also found this confusing") or simply give it a thumbs-up reaction. If an issue exists and was closed, check why (maybe it was fixed in a newer version, or deemed not an issue).

4. Prepare a (Hypothetical) Bug Report / Documentation Issue: Assuming no existing issue accurately covers the problem, formulate a clear report. If you were to create a new issue, you would aim for something like this:

  • Title: Docs: Missing list of named categories for [security] PASSWORD_COMPLEXITY
  • Type: Documentation Bug / Improvement
  • Gitea Version: Specify the version of Gitea you were using when consulting the docs (e.g., 1.21.5).
  • Description:
    • Context: "I was configuring password requirements using the PASSWORD_COMPLEXITY setting in app.ini."
    • Problem: "The documentation page [Link to the specific documentation page, e.g., https://docs.gitea.io/en-us/config-cheat-sheet/#security] explains how to use regular expressions for PASSWORD_COMPLEXITY. However, it does not seem to list the available named categories (like lower, upper, digit, special) which are also valid and simpler options."
    • Suggestion: "It would be helpful to explicitly list these named categories in the documentation alongside the regex option, perhaps with examples, to make configuration easier for users."
    • (Optional) Steps to Reproduce:
      (Not directly applicable for a docs issue, but you could mention where you looked).
      1. Visit the Config Cheat Sheet documentation.
      2. Find the [security] section.
      3. Read the description for PASSWORD_COMPLEXITY.
      4. Observe that named categories aren't listed."

5. (Self-Correction/Next Step): Before submitting, quickly check the source file for the documentation page if possible (often linked via an "Edit this page" button). Sometimes the information is there but rendered incorrectly, or perhaps it was added very recently. Also, double-check the sample app.ini in the Gitea source code; it often contains examples. If the categories are mentioned in the source or sample file, the issue might be different (rendering bug, outdated live docs site).

This process—identifying the specific issue, searching for duplicates, and formulating a clear, concise report with context and suggestions—makes it much easier for maintainers to understand and address the problem, whether it's a documentation gap or a software bug.

Conclusion

Throughout this guide, we have journeyed from the fundamentals of Gitea and self-hosting to advanced administration and customization on a Linux platform. We began with preparing the server environment, installing Gitea using the straightforward binary method, and performing the initial web configuration (Basic Setup).

We then progressed to securing the instance with HTTPS using Nginx and Let's Encrypt, exploring user management and LDAP integration, and harnessing the power of webhooks for automation. We also covered core development features like issue tracking and pull requests, along with migrating existing repositories (Intermediate Configuration).

Following that, we delved into critical operational aspects like robust backup and restore strategies using gitea dump, performance tuning through app.ini and system adjustments, monitoring via Gitea's metrics endpoint, understanding the concepts behind high availability, and customizing Gitea's appearance. We also established a systematic approach to troubleshooting common problems (Advanced Administration).

Finally, we expanded into integrating Gitea with external CI/CD tools, utilizing the built-in Gitea Actions, interacting programmatically via the Gitea API, exploring further advanced configurations, deploying Gitea with Docker, implementing security hardening techniques like Fail2ban, and engaging with the Gitea community (Ecosystem and Advanced Topics).

Benefits Revisited:

By successfully setting up and managing your own Gitea instance, you gain:

  • Full Control: Complete authority over your codebase, user access, data, and server environment.
  • Enhanced Privacy: Your sensitive development data stays within your infrastructure.
  • Customization: The ability to tailor Gitea's appearance, integrate it with other tools, and fine-tune its operation.
  • Cost Efficiency: Leverage powerful Git hosting and CI/CD capabilities without recurring subscription fees (beyond infrastructure costs).
  • Invaluable Learning: Practical experience in Linux administration, web server configuration, databases, CI/CD, containerization, security best practices, and Git server management.

The Path Forward:

Your self-hosted Gitea instance is now a powerful tool in your development arsenal. The journey involves continuous learning and maintenance:

  • Stay Updated: Keep Gitea and all underlying system components patched.
  • Monitor: Regularly check logs and system metrics for performance and security issues.
  • Backup Regularly: Ensure your backup strategy is working and test restores periodically.
  • Explore Further: Continue exploring Gitea's features, API capabilities, and community resources as your needs evolve.

Self-hosting Gitea is a rewarding endeavor that empowers you with a private, customizable, and efficient Git forge. The skills learned managing your own server and its associated services are highly transferable and valuable in many technical fields. Happy coding and hosting!