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:
-
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. -
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: true2. 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=1The 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
- 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" ]- Update the runner configuration to use host networking to be able to see the
forgejo:3000within docker network:
container:
network: "host"
privileged: trueExample 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 (likegit.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.