Codeberg: Cloud based Forgejo (OS Gitea)

Published: Dec 19, 2023 by Isaac Johnson

Codeberg is not only the Oganization that maintains Forgejo but also offers it as a cloud-based SaaS option as well. Codeberg (and Forgejo) are the long term Open-source forks of Gitea. Those familiar with Github will see similarities.

Who is Codeberg

Codeberg is a free and open-source Git hosting platform that provides a collaborative space for developers to create, maintain, and share their code. Founded in 2020 by a group of free software enthusiasts including Lukas Mackensie, René Meuse, Jan-Hendrik Möller, Tobias Bernhaupt, and Marius Vollmer. Codeberg is a non-profit organization that aims to provide a humane, non-commercial, and privacy-friendly alternative to commercial Git hosting services like GitHub.

Codeberg is primarily funded by donations from individuals and organizations that support its mission and values.

Register for Codeberg

First, we need to sign-up on the Register page

/content/images/2023/12/codeburg-01.png

This will then send a confirmation mail for us to click. I verified using my mobile and logged in

/content/images/2023/12/codeburg-02.jpg

I now have a blank landing page

/content/images/2023/12/codeburg-03.png

My next step is to create a repo. This one will be public just so we can test some features

/content/images/2023/12/codeburg-04.png

It’s now created with a .gitignore and README.md

/content/images/2023/12/codeburg-05.png

If we are going to want to use GIT with SSH, we’ll need to add our public key. We can do that in https://codeberg.org/user/settings/keys.

/content/images/2023/12/codeburg-06.png

Now that it’s added

/content/images/2023/12/codeburg-07.png

I can clone to the laptop

builder@LuiGi17:~/Workspaces$ git clone git@codeberg.org:idjohnson/MyFirstRepo.git CodeburgMyFirstRepo
Cloning into 'CodeburgMyFirstRepo'...
The authenticity of host 'codeberg.org (217.197.91.145)' can't be established.
ED25519 key fingerprint is SHA256:mIlxA9k46MmM6qdJOdMnAQpzGxF4WIVVL+fj+wZbw0g.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'codeberg.org' (ED25519) to the list of known hosts.
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (5/5), done.

I know I’m going to want to play with Actions soon so it’s worth enabling them now in the repository settings

/content/images/2023/12/codeburg-08.png

I can now get a token I can use with Codeburg Runners

/content/images/2023/12/codeburg-09.png

I’ll try to create a runner Deployment, Volume and Secret as I did for Gitea prior

$ cat codeburg-dind-runners.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: codeberg-runner-vol
spec:
  storageClassName: local-path
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
---
apiVersion: v1
data:
  token: RlR4dVo3SjUwdGFPVHdLZ211U0dPNlhQUGhMT1NscXNPVm9aZEs5SA==
kind: Secret
metadata:
  name: codeberg-runner-secret
type: Opaque
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
  labels:
    app: codeberg-runner
  name: codeberg-runner
spec:
  replicas: 2
  selector:
    matchLabels:
      app: codeberg-runner
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: codeberg-runner
    spec:
      containers:
      - command:
        - sh
        - -c
        - while ! nc -z localhost 2376 </dev/null; do echo 'waiting for docker daemon...';
          sleep 5; done; /sbin/tini -- /opt/act/run.sh
        env:
        - name: DOCKER_HOST
          value: tcp://localhost:2376
        - name: DOCKER_CERT_PATH
          value: /certs/client
        - name: DOCKER_TLS_VERIFY
          value: "1"
        - name: GITEA_INSTANCE_URL
          value: https://codeberg.org
        - name: GITEA_RUNNER_REGISTRATION_TOKEN
          valueFrom:
            secretKeyRef:
              key: token
              name: codeberg-runner-secret
        image: gitea/act_runner:nightly
        imagePullPolicy: IfNotPresent
        name: runner
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /certs
          name: docker-certs
        - mountPath: /data
          name: runner-data
      - env:
        - name: DOCKER_TLS_CERTDIR
          value: /certs
        image: docker:23.0.6-dind
        imagePullPolicy: IfNotPresent
        name: daemon
        resources: {}
        securityContext:
          privileged: true
        volumeMounts:
        - mountPath: /certs
          name: docker-certs
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      terminationGracePeriodSeconds: 30
      volumes:
      - emptyDir: {}
        name: docker-certs
      - name: runner-data
        persistentVolumeClaim:
          claimName: codeberg-runner-vol

I’ll fire it up in a cluster

$ kubectl apply -f codeburg-dind-runners.yaml
persistentvolumeclaim/codeberg-runner-vol created
secret/codeberg-runner-secret created
deployment.apps/codeberg-runner created

And almost immediately I see them pop up in my Codeberg Runner page

/content/images/2023/12/codeburg-10.png

Now I would guess that it assumes a token per runner since I see two runners in k8s (per the RS)

$ kubectl get pods | grep -i code
codeberg-runner-7fd6466fc7-dhv8s                         2/2     Running            0                  6m28s
codeberg-runner-7fd6466fc7-x8nqp                         2/2     Running            0                  6m28s

but still just the one on the site. To avoid confusion, I’ll scale that back to just one pod.

I want to test just a quick helloworld that should pick up that ubuntu-22.04 tag and one that has no restrictions that will try a Docker in Docker build

builder@LuiGi17:~/Workspaces/CodeburgMyFirstRepo$ cat .gitea/workflows/test01.yaml
name: Test01
run-name: $ building test
on: [push]

jobs:
  TestRunner:
    runs-on: ubuntu-22.04
    steps:
      - name: apt
        run: |
          hostname

          # if running as non-root, add sudo
          echo test
builder@LuiGi17:~/Workspaces/CodeburgMyFirstRepo$ cat .gitea/workflows/test02.yaml
name: test02
run-name: $ building test
on: [push]

jobs:
  TestRunner:
    steps:
      - name: Check out repository code
        uses: actions/checkout@v3
      - name: Setup Docker
        run: |
          # if running as non-root, add sudo
          apt-get update && \
          apt-get install -y \
             ca-certificates \
             curl \
             gnupg \
             sudo \
             lsb-release

          curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

          echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu focal stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null

          apt-get update

          apt install -y docker-ce-cli

          docker run hello-world
          #apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
      - name: Test Docker
        run: |
          # if running as non-root, add sudo
          echo hi
          docker ps

I’ll then add and push it back

builder@LuiGi17:~/Workspaces/CodeburgMyFirstRepo$ git add .gitea/workflows/
builder@LuiGi17:~/Workspaces/CodeburgMyFirstRepo$ git commit -m "Test two runners"
[main 1e8b8ef] Test two runners
 2 files changed, 49 insertions(+)
 create mode 100644 .gitea/workflows/test01.yaml
 create mode 100644 .gitea/workflows/test02.yaml
builder@LuiGi17:~/Workspaces/CodeburgMyFirstRepo$ git push
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 16 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 1.06 KiB | 361.00 KiB/s, done.
Total 6 (delta 0), reused 0 (delta 0), pack-reused 0
remote: . Processing 1 references
remote: Processed 1 references in total
To codeberg.org:idjohnson/MyFirstRepo.git
   2ce4904..1e8b8ef  main -> main

The first runner finished in no time

/content/images/2023/12/codeburg-11.png

My second got stuck on docer socket issues

/content/images/2023/12/codeburg-12.png

$ cat codeberg-dind-runners.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: codeberg-runner-vol
spec:
  storageClassName: local-path
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
---
apiVersion: v1
data:
  token: RlR4dVo3SjUwdGFPVHdLZ211U0dPNlhQUGhMT1NscXNPVm9aZEs5SA==
kind: Secret
metadata:
  name: codeberg-runner-secret
type: Opaque
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
  labels:
    app: codeberg-runner
  name: codeberg-runner
spec:
  replicas: 1
  selector:
    matchLabels:
      app: codeberg-runner
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: codeberg-runner
    spec:
      containers:
      - command:
        - sh
        - -c
        - (sleep 10 && chmod a+rwx /run/user/1000/docker.sock) & /usr/bin/supervisord -c /etc/supervisord.conf &&
          sleep 5 && /sbin/tini -- /opt/act/run.sh
        env:
        - name: DOCKER_HOST
          value: unix:///run/user/1000/docker.sock
#          value: tcp://localhost:2376
#        - name: DOCKER_CERT_PATH
#          value: /certs/client
#        - name: DOCKER_TLS_VERIFY
#          value: "1"
        - name: GITEA_INSTANCE_URL
          value: https://codeberg.org
        - name: GITEA_RUNNER_REGISTRATION_TOKEN
          valueFrom:
            secretKeyRef:
              key: token
              name: codeberg-runner-secret
        image: gitea/act_runner:nightly-dind-rootless
        imagePullPolicy: Always
        name: runner
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /certs
          name: docker-certs
        - mountPath: /data
          name: runner-data
        securityContext:
          privileged: true
      - env:
        - name: DOCKER_TLS_CERTDIR
          value: /certs
        image: docker:23.0.6-dind
        imagePullPolicy: IfNotPresent
        name: daemon
        resources: {}
        securityContext:
          privileged: true
        volumeMounts:
        - mountPath: /certs
          name: docker-certs
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      terminationGracePeriodSeconds: 30
      volumes:
      - emptyDir: {}
        name: docker-certs
      - name: runner-data
        persistentVolumeClaim:
          claimName: codeberg-runner-vol

However, as I did with Forgejo, we can launch into Docker

builder@builder-T100:~/Codeberg$ docker run -v /home/builder/Codeberg/config.yaml:/config.yaml -v /home/builder/Codeberg/data:/data -v /var/run/docker.sock:/var/run/docker.sock -e CONFIG_FILE=/config.yaml -e GITEA_INSTANCE_URL=https://codeberg.org/ -e GITEA_RUNNER_REGISTRATION_TOKEN=rafDrOTaUXAnydVblmr2SNBIJRZwdLxTVr24SZCh -e GITEA_RUNNER_NAME=buildert100 -e GITEA_RUNNER_LABELS=onprem --name buildert100cb -d gitea/act_runner:nightly
c1f96fc0c41d61dc439e83c2c5fae2d3b5ce6e79768a8274f5505d33be5fbc68

I can see it launched now

/content/images/2023/12/codeburg-13.png

This time it handled docker builds without issue

/content/images/2023/12/codeburg-14.png

Projects

Let’s setup a project and issue

In Projects we can set columns as default, remove them, or use the green “create column” to add new ones

/content/images/2023/12/codeburg-16.png

For instance, I can change a column name and colour

/content/images/2023/12/codeburg-17.png

Issues search/REST

I can search across public issues without auth

curl -X 'GET' \
  'https://codeberg.org/api/v1/repos/issues/search?owner=idjohnson' \
  -H 'accept: application/json'

We can add a -u user:password then see private repos

$ curl --silent -X 'GET' 'https://codeberg.org/api/v1/repos/issues/search
?owner=idjohnson' -H 'accept: application/json' | jq '.[] | .url'
"https://codeberg.org/api/v1/repos/idjohnson/MyFirstRepo/issues/1"

$ curl --silent -X 'GET' -u idjohnson:xxxxxxxx 'https://codeberg.o
rg/api/v1/repos/issues/search?owner=idjohnson' -H 'accept: application/json' | jq '.[] | .url'
"https://codeberg.org/api/v1/repos/idjohnson/MySecondRepo/issues/1"
"https://codeberg.org/api/v1/repos/idjohnson/MyFirstRepo/issues/1"

I can now create an issue with REST

$ curl -u idjohnson:xxxxxxx \
  -X 'POST' \
  'https://codeberg.org/api/v1/repos/idjohnson/MySecondRepo/issues' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "assignee": "idjohnson",
  "assignees": [
    "idjohnson"
  ],
  "body": "This is the body.",
  "closed": false,
  "title": "This is the title"
  }'
{"id":623986,"url":"https://codeberg.org/api/v1/repos/idjohnson/MySecondRepo/issues/2","html_url":"https://codeberg.org/idjohnson/MySecondRepo/issues/2","number":2,"user":{"id":138635,"login":"idjohnson","login_name":"","full_name":"Isaac Johnson","email":"idjohnson@noreply.codeberg.org","avatar_url":"https://codeberg.org/avatars/c08b65779558b88fbb0f12f726bc382d21e514feba44b0b928928f494205f262","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2023-11-29T00:42:25Z","restricted":false,"active":false,"prohibit_login":false,"location":"Woodbury, MN, USA","website":"https://freshbrewed.science","description":"Blogger and Cloud Architect.  Ice fisherman and father of amazing daughters.","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"idjohnson"},"original_author":"","original_author_id":0,"title":"This is the title","body":"This is the body.","ref":"","assets":[],"labels":[],"milestone":null,"assignee":{"id":138635,"login":"idjohnson","login_name":"","full_name":"Isaac Johnson","email":"idjohnson@noreply.codeberg.org","avatar_url":"https://codeberg.org/avatars/c08b65779558b88fbb0f12f726bc382d21e514feba44b0b928928f494205f262","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2023-11-29T00:42:25Z","restricted":false,"active":false,"prohibit_login":false,"location":"Woodbury, MN, USA","website":"https://freshbrewed.science","description":"Blogger and Cloud Architect.  Ice fisherman and father of amazing daughters.","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"idjohnson"},"assignees":[{"id":138635,"login":"idjohnson","login_name":"","full_name":"Isaac Johnson","email":"idjohnson@noreply.codeberg.org","avatar_url":"https://codeberg.org/avatars/c08b65779558b88fbb0f12f726bc382d21e514feba44b0b928928f494205f262","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2023-11-29T00:42:25Z","restricted":false,"active":false,"prohibit_login":false,"location":"Woodbury, MN, USA","website":"https://freshbrewed.science","description":"Blogger and Cloud Architect.  Ice fisherman and father of amazing daughters.","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"idjohnson"}],"state":"open","is_locked":false,"comments":0,"created_at":"2023-11-30T02:16:14Z","updated_at":"2023-11-30T02:16:14Z","closed_at":null,"due_date":null,"pull_request":null,"repository":{"id":165830,"name":"MySecondRepo","owner":"idjohnson","full_name":"idjohnson/MySecondRepo"},"pin_order":0}

Which we can see in the Project in our private repo

/content/images/2023/12/codeburg-18.png

Mirrors

While it’s been disallowed to mirror into Codeberg as there were frequent abusers of that, according to the administrators, we can mirror out of Codeberg.

I’ll create a new repo in my local Forgejo

/content/images/2023/12/codeburg-19.png

I’ll make it private, but leave the other settings default or off

/content/images/2023/12/codeburg-20.png

Which results in

/content/images/2023/12/codeburg-21.png

I can now add it as a mirror in Codeberg

/content/images/2023/12/codeburg-22.png

I’ll then click “Synchronize Now”

/content/images/2023/12/codeburg-23.png

And see a notice pop up

/content/images/2023/12/codeburg-24.png

Which shows up in Forgejo moments later

/content/images/2023/12/codeburg-25.png

I’m curious though, what if I enable Actions in both?

I enabled actions in my local Forgejo

/content/images/2023/12/codeburg-26.png

I then made a small change to an Actions file in Codeberg

/content/images/2023/12/codeburg-27.png

Then commit it

/content/images/2023/12/codeburg-28.png

and both fired off!

/content/images/2023/12/codeburg-29.png

What this means is we can do some clever things such as have source code that lives in one system, such as Codeberg but disable action runners there. We can have a mirror locally and run builds just inside our network.

I could see doing that for an open-source project that I want to share the code, and even enable some builds, but keep the details of those build outputs private.

I was curious if it really would block mirror pushes so I set up a Codeberg mirror in Forejo to see if I could go in the reverse direction.

Indeed it pushed wihtout issue

/content/images/2023/12/codeburg-30.png

This leads me to believe they might block abuses or mirrors from big sites like Gitlab and Github, but not from homelab Forgejo/Gitea instances.

Summary

I really like Codeberg and to the larger extent Forgejo. The fact is, provided I can mirror content, I feel much safer in diving in with true Open-Source source code and CICD systems.

Don’t get me wrong, I like Github and Gitlab. I know people at Github and I hear nothing but great things. Companies that treat their people right have weight with me. But it doesn’t change the fact Github is for-profit. It’s aim is to get me to spend, and I have no bones to pick there. To date, that is mostly around getting me to pony up for AI side tools which I won’t do. But there could be a date they decide to ditch or restrict freeloaders like myself.

When we lean in on Open-Source systems there are a few big benefits: We have autononmy. We control our destinies. We can contribute money and or time to improving things. We can build scalable connected systems without worry about license shifts (such as when a major IaC vendor ditches OS and we all have to fork to OpenToFu or Pulumi).

So back to the topic of Codeberg. If our code is synced, then the question becomes: Do we find ways to replicate our projects, boards and settings? I might argue settings can be documented as well as projects. We can also replicate to multiple Issues systems or take occasional dumps for safety.

For example, today, I leverage Github Issues for tracking work for this Blog. However, the feedback form linked at the top of the site sends tickets into cloud-based Atlassian JIRA, Github and a short summary to a variety of notification systems. When things like my GH token expired or I had a bug somewhere, I was still okay because there was a second copy.

In the olden days, JIRA was a Java app one hosted internally. We would often do JIRA exports to CSV which didn’t include attachments, but had most everything else, and that solved our “DR” strategy. My Forgejo is in Postgres in an HA NAS, but my house could burn down and then I would be without a copy - so there is likely value in shoving some of that into some form of long term cloud storage (or mailing a replica NAS to family cross country).

Codeberg Forgejo OpenSource Kubernetes Docker

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes