DEV Community

Cover image for Setting Up Your Own Git and CI Platform with Forgejo Actions
Mustafa ERBAY
Mustafa ERBAY

Posted on • Originally published at mustafaerbay.com.tr

Setting Up Your Own Git and CI Platform with Forgejo Actions

Forgejo is a lightweight, self-hostable Git service that emerged from a fork of the Gitea project. While providing a centralized code repository for development teams, its integrated Forgejo Actions feature enables the automation of CI/CD (Continuous Integration/Continuous Delivery) processes directly within the Git platform. In this post, we will walk through how to install and configure Forgejo and Actions on your own server and optimize your software development processes.

Setting up your own Git and CI platform offers significant advantages, especially in terms of data sovereignty, customization flexibility, and cost control. Forgejo provides a highly suitable solution to meet this need.

Why Forgejo and Forgejo Actions?

Reliance on external Git and CI/CD providers can sometimes lead to issues such as data sovereignty concerns, security policy limitations, or customization restrictions. For organizations working with sensitive data or subject to strict regulations, maintaining control over their codebase and CI/CD processes is critical. Forgejo is a lightweight and fast alternative written in Go, developed to provide this control.

Forgejo Actions, inspired by GitHub Actions, is an automation engine directly integrated into Forgejo. This allows you to host your code and manage processes like building, testing, and deployment all within the same platform. This integration simplifies the development workflow and reduces the toolchain.

ℹ️ Pragmatic Approach

Self-hosting may not always be the best solution. However, if there are specific needs (data security, cost optimization, high customization requirements), solutions like Forgejo offer a very powerful option. It provides a platform that can be quickly set up, especially for small and medium-sized teams.

In some corporate projects, keeping code and data off-premises is not a preference but a necessity. Forgejo offers a single, lightweight solution that can be used as both a Git server and an integrated CI/CD tool in such scenarios. This significantly reduces the burden of setting up and managing separate Git and CI/CD servers.

Forgejo Installation: Basic Steps

One of the most practical and flexible ways to install Forgejo is by using Docker and docker-compose. This method simplifies dependency management and makes the installation repeatable. Before starting, ensure that Docker and docker-compose are installed on your server and that you have a domain name for Forgejo (e.g., git.domain.com) along with its SSL/TLS certificates (e.g., from Let's Encrypt).

We begin by creating a docker-compose.yml file. This file will include the Forgejo service, a PostgreSQL database, and an Nginx reverse proxy. Nginx will handle SSL termination and route incoming requests to Forgejo.

# docker-compose.yml
version: "3"

services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:16 # Updated to current stable version 16.0.0.
    container_name: forgejo
    restart: always
    environment:
      - DB_TYPE=postgres
      - DB_HOST=db:5432
      - DB_USER=forgejo
      - DB_PASS=your_db_password
      - DB_NAME=forgejo
      - FORGEJO__server__DOMAIN=git.domain.com # GITEA__ prefixes updated to FORGEJO__.
      - FORGEJO__server__SSH_DOMAIN=git.domain.com
      - FORGEJO__server__SSH_PORT=2222 # Port Forgejo will use for SSH
      - FORGEJO__server__HTTP_PORT=3000
      - FORGEJO__server__ROOT_URL=https://git.domain.com/
      - FORGEJO__service__DISABLE_REGISTRATION=false # Can be set to true if desired
      - FORGEJO__actions__ENABLED=true # Correct variable to enable Actions runners.
      # - GITEA__RUNNER__DISABLE_BUILTIN_ACTIONS=false # Removed as there is no direct equivalent in Forgejo documentation.
    volumes:
      - ./forgejo-data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "127.0.0.1:3000:3000" # For Nginx access only
      - "127.0.0.1:2222:22" # For SSH, Nginx will not use this
    depends_on:
      - db

  db:
    image: postgres:15 # PostgreSQL 15 is a current and stable version.
    container_name: forgejo_db
    restart: always
    environment:
      - POSTGRES_USER=forgejo
      - POSTGRES_PASSWORD=your_db_password
      - POSTGRES_DB=forgejo
    volumes:
      - ./postgres-data:/var/lib/postgresql/data

  nginx:
    image: nginx:stable-alpine
    container_name: forgejo_nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
    depends_on:
      - forgejo
Enter fullscreen mode Exit fullscreen mode

After creating this docker-compose.yml file, you need to prepare an Nginx configuration file (nginx/nginx.conf) and your SSL certificates (nginx/ssl/fullchain.pem, nginx/ssl/privkey.pem). The Nginx configuration typically involves routing incoming HTTPS requests to port 3000 of the Forgejo container. For example:

# nginx/nginx.conf
events {
    worker_connections 1024;
}

http {
    server {
        listen 80;
        server_name git.domain.com;
        return 301 https://$host$request_uri;
    }

    server {
        listen 443 ssl http2;
        server_name git.domain.com;

        ssl_certificate /etc/nginx/ssl/fullchain.pem;
        ssl_certificate_key /etc/nginx/ssl/privkey.pem;
        ssl_session_timeout 1d;
        ssl_session_cache shared:SSL:10m;
        ssl_session_tickets off;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
        ssl_prefer_server_ciphers off;

        location / {
            proxy_pass http://forgejo:3000;
            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;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Once these files are prepared, you can start all services with the docker-compose up -d command. On the first run, you will need to access Forgejo via the web interface and create the first administrator account. Ensure that the database connection details match those in your docker-compose.yml file.

Preparing the Forgejo Actions Environment

To use Forgejo Actions, we need "runners" to execute workflows. Runners work similarly to self-hosted runners in GitHub Actions. By setting up one or more runners on your own server, you can run Forgejo Actions jobs on these machines. This is ideal for jobs that require specific hardware (like GPUs) or network isolation.

To set up a runner, first log in to the Forgejo web interface. You can add a new runner from the admin panel or from the "Settings > Actions > Runners" section in a repository. Here, you will be provided with a registration token and commands to set up the runner.

Diagram

On the machine where you will set up the runner (this can be the same server as Forgejo or a different one), follow these steps:

  1. Download the Runner Binary: Download the appropriate forgejo-runner binary for your operating system according to the instructions provided by Forgejo. For example, for Linux:

    # Check the latest stable version and update the download URL.
    # The latest version of forgejo-runner is v16.0.0.
    wget https://codeberg.org/forgejo/forgejo-runner/releases/download/v16.0.0/forgejo-runner-linux-amd64 # Version updated to v16.0.0.
    mv forgejo-runner-linux-amd64 forgejo-runner
    chmod +x forgejo-runner
    

    Remember to replace the version number with the latest version recommended by your Forgejo instance.

  2. Register the Runner: Register the runner with Forgejo using the downloaded binary. This command will prompt you for information such as the Forgejo URL, registration token, and labels you want to assign to the runner (e.g., linux, amd64, docker).

    ./forgejo-runner register
    

    Once these steps are complete, the runner will appear as active in your Forgejo instance.

  3. Run the Runner as a Service: To ensure the runner runs continuously, configuring it as a systemd service is the best approach. A systemd unit file ensures the runner starts automatically and restarts in case of failure.

    # /etc/systemd/system/forgejo-runner.service
    [Unit]
    Description=Forgejo Actions Runner
    After=network.target
    
    [Service]
    Type=simple
    User=runner # It is recommended to create a separate 'runner' user for the runner.
    ExecStart=/opt/forgejo-runner/forgejo-runner daemon
    Restart=always
    WorkingDirectory=/opt/forgejo-runner
    
    [Install]
    WantedBy=multi-user.target
    

    After creating this file, move the runner binary to the /opt/forgejo-runner/ directory, create a user named runner, and grant the necessary permissions. Then, start the service with sudo systemctl enable forgejo-runner and sudo systemctl start forgejo-runner commands.

⚠️ Runner Security

Runners pose potential security risks as they execute your code. It is important to run runners in separate virtual machines or isolated Docker environments. Using cgroup limits for resource constraints can prevent runners from consuming system resources, especially in shared environments.

Don't forget to check the status of your runners from the Forgejo web interface to ensure they are healthy. You can add multiple runners to distribute the workload and increase parallel execution.

Creating Your First Forgejo Actions Workflow

Forgejo Actions workflows are defined as YAML files under the .forgejo/workflows directory in your repository. This structure is largely similar to GitHub Actions, meaning you can leverage your existing GitHub Actions knowledge. A workflow file defines one or more jobs that will be triggered by specific events (e.g., push, pull request) and the steps to be executed within each job.

Let's create a simple test workflow for an example Python project. This workflow will be triggered on every push event, set up the Python environment, install dependencies, and run tests.

# .forgejo/workflows/python_test.yml
name: Python Test and Build

on:
  push:
    branches:
      - main
      - develop
  pull_request:
    branches:
      - main
      - develop

jobs:
  build_and_test:
    runs-on: linux # Enter your runner's label here, e.g.: self-hosted, linux, docker.
    steps:
      - name: Checkout Code
        uses: actions/checkout@v6 # actions/checkout@v6 updated to current version.

      - name: Set up Python Environment
        uses: actions/setup-python@v5 # Available and current version on data.forgejo.org.
        with:
          python-version: '3.9'

      - name: Install Dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt

      - name: Run Tests
        run: |
          pytest

      - name: Build Application (Example)
        run: |
          echo "Building application..."
          # A real build command could go here
          # python setup.py sdist bdist_wheel

      - name: Store Compiled Files (Artifact)
        uses: actions/upload-artifact@v4 # Available and current version on data.forgejo.org.
        with:
          name: my-app-dist
          path: dist/ # Assumed directory for build output
Enter fullscreen mode Exit fullscreen mode

The core concepts used in this example are:

  • name: The display name of the workflow.
  • on: Determines when the workflow will be triggered. push and pull_request are the most common. You can target specific branches.
  • jobs: Logical units that will run in parallel or sequentially within the workflow. Each job must have its own runs-on property.
  • runs-on: Specifies which runner the job will run on. You can use general labels like docker or custom labels you've given to your runner (self-hosted, my-custom-runner).
  • steps: Commands or actions that will run sequentially within a job.
  • name: The display name of the step.
  • uses: To use an existing Actions action. actions/checkout@v6 is used to checkout code, actions/setup-python@v5 to set up the Python environment.
  • run: To execute shell commands directly on the runner.
  • with: To pass parameters to the action or command being used.

💡 Fast Feedback

Running tests that can fail quickly in the initial steps of your CI/CD workflows provides faster feedback to developers. For example, placing static code analysis or linter checks at the beginning is a good strategy to catch basic errors before proceeding to lengthy integration tests.

When you add this workflow file to your repository and push it, Forgejo Actions will automatically trigger and run the defined steps on the runner. You can track the status and logs of the process from the "Actions" tab in the Forgejo interface.

Advanced Forgejo Actions Scenarios and Tips

Forgejo Actions offers advanced features to support more complex CI/CD scenarios beyond simple build and test processes. These include dependency caching, matrix strategies, secret management, and custom deployment mechanisms. These features allow you to make your workflows more efficient, flexible, and secure.

Dependency Caching

Re-downloading dependencies (npm modules, pip packages, etc.) every time a workflow runs can be time-consuming. Forgejo Actions can speed up subsequent runs by caching these dependencies with the actions/cache action.

# Example dependency caching step
- name: Load Cache (Pip)
  uses: actions/cache@v4 # Available and current version on data.forgejo.org.
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-

- name: Install Dependencies
  run: pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

This step creates a cache key based on the hash of the requirements.txt file. If the key matches, it restores from the cache; otherwise, it installs dependencies and creates a new cache. This significantly reduces CI times, especially for projects with large dependency sets.

Matrix Strategies

Matrix strategies are very useful when you need to run the same job in different environments (e.g., different Python versions, operating systems). This allows you to test multiple combinations with a single workflow definition.

jobs:
  test:
    runs-on: linux # Enter your runner's label here.
    strategy:
      matrix:
        python-version: ['3.8', '3.9', '3.10']
    steps:
      - uses: actions/checkout@v6
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install dependencies
        run: pip install -r requirements.txt
      - name: Run tests
        run: pytest
Enter fullscreen mode Exit fullscreen mode

This matrix will run the test job separately for each Python version.

Secrets Management

Writing sensitive information such as database connection strings, API keys, or SSH keys directly into your workflow files poses a security risk. Forgejo allows you to define secrets at the repository or organization level. These secrets can be used in your workflows as secrets.MY_SECRET.

🔥 Protecting Sensitive Information

Secrets are never displayed as plain text in workflow logs. However, be careful not to accidentally print these variables with an echo command. Also, you should carefully manage the runners' access to this sensitive information; an unauthorized runner should be prevented from accessing sensitive data.

Deployment Scenarios

Deployment with Forgejo Actions typically involves connecting to a server via SSH and running commands, or transferring files with tools like rsync. You can define an SSH key as a secret, and then use this key on the runner to access the target server and run deployment commands.

# Example deployment step
- name: Deploy Application to Server
  env:
    SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_KEY }}
  run: |
    mkdir -p ~/.ssh
    echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
    chmod 600 ~/.ssh/id_rsa
    # CAUTION: StrictHostKeyChecking=no carries security risks and should only be used in secure, controlled environments.
    # In production environments, known host keys should be used or a more secure authentication method should be preferred.
    # This command connects to 'your-server.com' as user 'user' on the remote server.
    # It is recommended to always backup before deployment and consider dry-run options.
    ssh -o StrictHostKeyChecking=no user@your-server.com "cd /var/www/my-app && git pull && systemctl restart my-app.service"
Enter fullscreen mode Exit fullscreen mode

This example connects to the remote server using an SSH key and runs deployment commands there. For more complex deployment scenarios, you can run automation tools like Ansible or Fabric on the runner.

These advanced scenarios transform Forgejo Actions from just a testing tool into a full-fledged CI/CD platform. The flexibility provided by self-hosting allows you to tailor these processes to your specific needs.

Maintenance, Security, and Performance Optimization

Setting up a self-hosted Git and CI/CD platform brings with it ongoing responsibilities for maintenance, security management, and performance optimization. These aspects are critical to ensuring the system runs stably, securely, and efficiently.

Ongoing Maintenance

It is important to keep Forgejo and its runners regularly updated. New versions typically include bug fixes, security patches, and performance improvements.

  • Forgejo Updates: Pulling the Docker image and re-running docker-compose up -d is usually sufficient. However, for major version upgrades, database schema changes may occur, so carefully reading the update notes and taking backups is vital.
  • Runner Updates: You may need to download the runner binary, replace the existing one, and restart the systemd service.
  • Backup: Regularly backing up the Forgejo database (PostgreSQL) and the forgejo-data folder is fundamental to your disaster recovery plan. Creating automated backup scripts and storing backups in a different storage location is a best practice.

Security Management

Like any service running on your own server, Forgejo and its runners must be managed carefully from a security perspective.

  • Network Segmentation: Keeping Forgejo and runners in separate VLANs if possible provides network-level isolation. Defining firewall rules that only allow runners to communicate with Forgejo is important.
  • Access Control: Ensure that only authorized personnel can access the Forgejo panel. Restrict SSH access to the servers where runners are running and implement strong password/key policies.
  • CVE Tracking: Monitoring CVE notifications for the core components used by Forgejo (Go runtime, PostgreSQL, Docker) and runners helps in early detection of potential security vulnerabilities. System-level hardening steps like kernel module blacklisting can also enhance overall security.
  • Audit Logs: Regularly monitoring Forgejo's and the server's audit logs helps detect suspicious activity. You can track system events with journald or auditd.

Performance Optimization

Performance optimization is crucial for your workflows to run quickly and efficiently.

  • Database Tuning: PostgreSQL is the backbone of Forgejo. Adjusting parameters like shared_buffers, work_mem in the postgresql.conf file according to your server's resources significantly impacts database performance. Monitoring WAL bloat and performing regular VACUUM operations are also important.
  • Runner Resources: Ensure runners have sufficient CPU, RAM, and disk I/O. Especially if many workflows are running concurrently, it may be necessary to scale runners or use more powerful machines. You can prevent runners from overusing system resources by using cgroup limits.
  • Caching: As mentioned above, dependency caching plays a big role in reducing workflow durations.
  • Parallel Execution: Logically dividing your workflows to run in parallel reduces the overall CI/CD time. Matrix strategies can help with this.
  • Disk Performance: The performance of the disks where Docker containers run directly affects build and test times. Using fast SSDs makes a difference in such I/O-intensive workloads.

When self-hosting Forgejo, not overlooking these maintenance, security, and performance considerations will ensure you have a more robust and reliable platform in the long run.

Conclusion

Setting up your own Git and CI/CD platform with Forgejo Actions is a powerful way to gain full control over your development processes, ensure data sovereignty, and optimize costs in the long term. Its lightweight nature, easy installation, and familiar Actions workflow definition make it a versatile solution suitable for a wide range of organizations, from small teams to larger enterprises.

In this guide, we covered the fundamental steps from Forgejo installation, configuring Actions runners, creating your first workflow, to advanced scenarios and system maintenance. The flexibility and customization options offered by self-hosting allow you to tailor your software development practices to your specific needs. The next step will be to design custom workflows for your projects and unlock the full potential of Forgejo Actions.

Official Resources

Top comments (0)