Setting Up a CI runner with Forgejo Actions

tags

After Setting Up a Self-Hosted Git Server with Forgejo, implementing automated workflows through Actions becomes essential for continuous integration and deployment. In this article, we’ll explore how to set up a continuous integration (CI) runner using Forgejo Actions as well as publishing a docker image to Foregjo container repository.

Understanding Container Orchestration Approaches

Firstly, let’s understand the key difference between two common approaches to Docker integration in CI runners. Gitea and Forgejo each take a distinct path:

  1. Gitea’s example uses a direct mount approach, where the host’s Docker socket (/var/run/docker.sock) is directly accessible to the runner. This is simpler but provides less isolation.

  2. Forgejo’s example uses Docker-in-Docker (DinD), which creates a complete Docker environment inside another Docker container. While this requires more configuration, it offers superior isolation and security.

Setting Up the Infrastructure

Let’s break down the setup into manageable steps:

1. Docker Compose Configuration

Our Docker Compose configuration creates two essential services: the Docker-in-Docker daemon and the Forgejo runner. Here’s the configuration with explanations of each component:

services:
  # The Docker-in-Docker service provides an isolated Docker environment
  docker-in-docker:
    image: docker:dind
    privileged: true
    command: [ "dockerd", "-H", "tcp://0.0.0.0:2375" ]
    networks:
      - forgejo
    restart: 'unless-stopped'
  
  # The runner service executes our CI/CD workflows
  runner:
    image: 'code.forgejo.org/forgejo/runner:3.3.0'
    links:
      - docker-in-docker
    depends_on:
      docker-in-docker:
        condition: service_started
    environment:
      DOCKER_HOST: tcp://docker-in-docker:2375
    volumes:
      - ./config/runner/.runner:/data/.runner
      - ./config/runner/config.yml:/data/config.yml
      - runner-data:/data
    networks:
      - forgejo
    restart: 'unless-stopped'
    command: '/bin/sh -c "sleep 5; forgejo-runner -c /data/config.yml daemon"'
 
volumes:
  runner-data:
 
networks:
  forgejo:
    external: true

2. Runner Configuration

After registering your runner with Forgejo, you’ll need to configure two key files:

The Runner Configuration File (config.yml)

This file controls how the runner behaves and interacts with Docker:

log:
  level: info
runner:
  file: .runner
  capacity: 1
cache:
  enabled: true
container:
  docker_host: "tcp://docker-in-docker:2375"
  options: --env=DOCKER_HOST=tcp://docker-in-docker:2375 --env=DOCKER_BUILDKIT=1

The Runner Identity File (.runner)

This file contains the runner’s identity and capabilities:

{
  "WARNING": "This file is automatically generated by act-runner. Do not edit it manually unless you know what you are doing. Removing this file will cause act runner to re-register as a new runner.",
  "id": 4,
  "uuid": "9fa24b03-bae0-49d6-a7be-97e5221cf569",
  "name": "git-runner",
  "token": "83d3a26befdb206d136d6520767d32a1767d6a94",
  "address": "https://git.zloutek1.com",
  "labels": [
    "ubuntu-20.04:docker://catthehacker/ubuntu:act-22.04",
    "ubuntu-latest:docker://catthehacker/ubuntu:act-latest"
  ]
}

Working with Local Docker Registry

When setting up a CI pipeline that builds and publishes Docker images, you might encounter some challenges with the local registry. Here’s how to address issues that I have encountered:

Configuring for Local Registry Access

  1. First, modify your Docker-in-Docker service to accept insecure registries:
docker-in-docker:
    image: docker:dind
    command: [ "dockerd", "-H", "tcp://0.0.0.0:2375", "--tls=false", "--insecure-registry=forgejo:3000" ]
  1. Update the runner configuration to use host networking to be able to see the forgejo:3000 within docker network:
container:
  network: "host"
  privileged: true

Example CI Pipeline

Here’s a complete example of a CI pipeline that builds and publishes a Docker image:

name: Build and Test
 
on:
  push:
    branches:
      - main
 
jobs:
  
  build-and-test:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
 
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
 
      - name: Cache dependencies
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-
 
      - run: npm ci
 
      - name: Check types and style
        run: npm run check
 
      - name: Test
        run: npm test
 
      - name: Ensure Quartz builds, check bundle info
        run: npx quartz build --bundleInfo
 
  publish:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository
        uses: actions/checkout@v3
        with:
          submodules: true
 
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
        with:
          driver-opts: network=host
          config-inline: |
            [registry."forgejo:3000"]
            http = true
            insecure = true
 
      - name: Login docker registry
        uses: docker/login-action@v3
        with:
          registry: "forgejo:3000"
          username: ${{ secrets.PRIVATE_REGISTRY_USERNAME }}
          password: ${{ secrets.PRIVATE_REGISTRY_PASSWORD }}
 
      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: "forgejo:3000/zloutek1/blog:${{gitea.sha}},forgejo:3000/zloutek1/blog:latest"

Important Considerations

When working with this setup, keep in mind:

  • The Docker-in-Docker approach provides better isolation but requires more careful configuration
  • Local registries may need special handling for HTTP communication
  • Network configuration is crucial for proper communication between services
  • Secrets should be properly managed through the Forgejo interface
  • Access to local registries might need to be via docker names (like forgejo:3000) instead of public web (like git.zloutek1.com) because of limits of the proxy (like Clourflare)

This setup provides a robust foundation for running CI/CD pipelines with Forgejo Actions, offering both security and flexibility for your development workflow.

References

Installing Forgejo with a separate runner

Forgejo - OS GIT