Have you ever heard that you can easily set up a fully self-hosted CI/CD pipeline with a fully customized personal git instance? If your answer is no and you're interested in learning how to set it up quickly, then this article is for you. And of course, if you're a person who cares about privacy, this tutorial could help you expand your toolkit for Git.
As always, we will begin by learning all the basics about Forgejo and the base features that are provided by this open-source self-hosted Git instance (or even better, call it a GitHub analog).
Introduction: What is Forgejo and how to set up your instance fast?
Previously, I already published a starter guide on how you can quickly set up a self-hosted Forgejo instance with privacy-friendly settings. In this guide, we are going to follow the same steps, but it will be more expanded, as we will run more containers to support self-hosted CI/CD pipelines.
But first, let me answer the fundamental question: what is Forgejo? Briefly, Forgejo is a fully self-hosted Git application like Bitbucket, GitLab, or GitHub. The difference is that you control all the data and you can do whatever you want. A lot of teams use solutions like that for their internal Git system to protect code from breaches, AI, or just for privacy concerns.
Fast setup with Docker
Of course, you can set up Forgejo without Docker or Podman, but it will be much harder than using a solution with containers. We are going to begin by creating a new Docker Compose file (also don't forget to create a separate folder for your instance):
version: "3.8"
networks:
forgejo:
external: false
services:
db:
image: postgres:16-alpine
container_name: forgejo_db
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- ./postgres:/var/lib/postgresql/data
networks:
- forgejo
forgejo:
image: codeberg.org/forgejo/forgejo:16
container_name: forgejo
environment:
- USER_UID=1000
- USER_GID=1000
- FORGEJO__database__DB_TYPE=postgres
- FORGEJO__database__HOST=db:5432
- FORGEJO__database__NAME=${POSTGRES_DB}
- FORGEJO__database__USER=${POSTGRES_USER}
- FORGEJO__database__PASSWD=${POSTGRES_PASSWORD}
- FORGEJO__actions__ENABLED=true # this is important to enable CI/CD
volumes:
- ./forgejo:/data
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "2222:22"
depends_on:
- db
networks:
- forgejo
# And yes, we are going to run docker-in-docker (what a surprise!)
docker-in-docker:
image: docker:dind
container_name: forgejo_dind
privileged: true
restart: unless-stopped
command: ["dockerd", "-H", "tcp://0.0.0.0:2375", "--tls=false"]
healthcheck:
test: ["CMD", "docker", "-H", "tcp://127.0.0.1:2375", "info"]
interval: 2s
timeout: 5s
retries: 15
networks:
- forgejo
runner:
image: data.forgejo.org/forgejo/runner:13
container_name: forgejo_runner
restart: unless-stopped
depends_on:
docker-in-docker:
condition: service_healthy
forgejo:
condition: service_started
environment:
DOCKER_HOST: tcp://docker-in-docker:2375
user: "1001:1001"
volumes:
- ./runner-data:/data
networks:
- forgejo
command: forgejo-runner daemon --config /data/runner-config.yml
volumes:
forgejo_db_data:_db_data:
I know what you're thinking after you saw that there will be a docker-in-docker container and you already are calling DevOps police, but let me explain this part briefly. Forgejo runner (container that will run CI/CD) can't run Docker commands inside its own container for the workflows, and to solve this problem, there are two possible solutions.
First solution: we can create a new Docker instance inside Docker so it can be used by the Forgejo runner as we just did.
Second solution: nest a host's UNIX socket and use it to run Docker commands.
For our case, it will be easier and better to use the docker-in-docker solution, as we are already running a Forgejo instance with Docker.
Installation and configuring all configs
After the docker-compose.yaml is created, the next step will be to create a .env file in the same folder where docker compose and paste the following variables:
POSTGRES_DB=forgejo_db
POSTGRES_USER=forgejo_db
POSTGRES_PASSWORD=forgejo_pass_123
Don't forget to generate a strong password in case you're going to host this in your homelab or VPS server. Don't run all containers at once; first, you need to start only the database and forgejo containers:
$ docker compose up -d db forgejo
If there are no errors in the container logs, then you can continue and move into the installation step. Open https://localhost:3000, and you should see the following page:
All installations can be easily completed via this page if you don't want to do configuration via a configuration file. Also, you can fill in additional settings if you need, for example, an SMTP server for your git instance. If no need that, then scroll down and open the "Administrator account settings":
Just fill in your administrator account credentials and save them, and after that, just click on "Install Forgejo" to start the installation process. It can take a few seconds; after that, you should see the dashboard page:
Great, now get back into your terminal and run the following command to create a Forgejo runner config:
$ mkdir -p runner-data/.cache
$ docker run --rm data.forgejo.org/forgejo/runner:13 \
forgejo-runner generate-config > runner-data/runner-config.yml
Okay, now you'll need to open again Forgejo UI and register the runner - click on the avatar in the top right corner -> Site administration -> Actions -> Runners -> Create new runner.
Just type the runner name and create. Copy the UUID and token - you will need this for runner configuration:
Open your editor and open the following file: runner-data/runner-config.yml, and find the "server" variable and just paste the runner configuration inside the "connections" variable:
container:
network: "host" # edit this variable too
server:
connections:
forgejo:
url: http://forgejo:3000/ # Important, type here forgejo, not localhost
uuid: cfb0f9aa-afff-40d3-aad2-077e9db72584
token: edccd714edb2c15ca43edf6e851f64cee0c495d7
It's time to start all containers; just run the following command:
$ docker compose up -d
$ docker compose logs -f runner # run this to check logs of forgejo runner container
If everything is okay, you should see the following message inside the Forgejo runner container:
time="2026-08-20T06:13:51Z" level=info msg="[poller] launched"
Creating the first workflow
We are going to create a pretty simple deployment workflow just to see that it will be triggered after we push some changes into repositories that we host inside our Forgejo instance. But before you run your first deployment, we need to have something we can deploy, and first we need a new repository.
Just open the dashboard and click on the plus button and "New repository":
Like on GitHub, you type all the information and settings here:
And after that, just follow the steps to clone the repository into your local machine:
After the repository is cloned, open it in the IDE and create the following folders and file with the workflow .forgejo/workflows/demo.yml:
on: [push]
jobs:
test:
runs-on: docker
steps:
- uses: https://code.forgejo.org/actions/checkout@v4
- run: echo "Forgejo + Actions is alive"
In short, this workflow will be triggered only when we push new changes into the repository, and it will just log a "Forgejo + Actions is alive" message in the action logs. And don't forget to commit your changes:
$ git add .
$ git commit -m "first commit"
$ git remote add origin http://localhost:3000/runneruser/myrepo.git # don't forget to type your repo URL here
$ git push -u origin main
It will ask for credentials from your Forgejo account; just type your admin account credentials:
Go back to the repository page and open the "Actions" tab; you should see that the action was triggered:
You can also check progress logs for each step:
And if everything went well, you will see green checkmarks in all steps:
Congratulations! You just created your first workflow and fully self-hosted CI/CD with custom deployments; now it's time to discuss what we should do next and other capabilities in Forgejo.
What's next?
So now you got full understanding of how you can do this, and the next question is - what should I do next? Just like on GitHub, you can use similar workflow schemas to create your deployment workflows that will be executed via the Forgejo runner and a Docker-in-Docker container. If you don't have any experience with this topic, I really recommend trying to set up a custom CI/CD deployment for a simple Node.js application inside an EC2 instance.
More about Forgejo, workflows, CI/CD, and self-hosting, I'm going to explain and show in the next article series - for now, thank you for reading this guide.













Top comments (0)