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
This will then send a confirmation mail for us to click. I verified using my mobile and logged in
I now have a blank landing page
My next step is to create a repo. This one will be public just so we can test some features
It’s now created with a .gitignore and README.md
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.
Now that it’s added
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
I can now get a token I can use with Codeburg Runners
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
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
My second got stuck on docer socket issues
$ 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
This time it handled docker builds without issue
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
For instance, I can change a column name and colour
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
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
I’ll make it private, but leave the other settings default or off
Which results in
I can now add it as a mirror in Codeberg
I’ll then click “Synchronize Now”
And see a notice pop up
Which shows up in Forgejo moments later
I’m curious though, what if I enable Actions in both?
I enabled actions in my local Forgejo
I then made a small change to an Actions file in Codeberg
Then commit it
and both fired off!
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
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).