Ubuntu Tutorial: Jenkins + Gitea + Docker

0 - Introduction

If you’re looking to implement a CI/CD workflow and optimize your testing environment, there is no better open-source tool than Jenkins. In this article we will use Jenkins alongside Gitea to automate the building, testing, and deploying of applications.

With Gitea as a lightweight, self-hosted, Git service and Jenkins as our go-to for continuous integration, this setup is ideal for homelabs, personal projects or even small development teams.

Before starting the setup, ensure Docker is installed, as it’s necessary for hosting Gitea and Jenkins. You can learn how to setup Docker on this article. Note that this tutorial will be a little longer than usual, as we will setup two services and also an example application repository.

1 - Setup Gitea

Make a folder for gitea and, in it, create a docker compose file:

mkdir gitea
cd gitea
nano docker-compose.yml

In this file, we give Gitea a data folder, timezone and localtime and expose ports 3000 (Webpage) and 8022 (SSH):

networks:
  gitea:
    external: false

services:
  server:
    image: gitea/gitea:latest
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
    restart: always
    networks:
      - gitea
    volumes:
      - ./data/gitea:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "8022:22"

After saving the file (CTRL+O and CTRL+X on nano), start the container with:

docker compose up

Wait a little until the service starts up, should not take more than a minute or two, then press CTRL+C to exit and edit the config file:

sudo nano data/gitea/gitea/conf/app.ini

In this config you will need to allow webhooks from inside your network. You can either specify a computer or a network, just copy the one you want and paste it at the bottom of the config file and edit the IP or network to match your own:

# 10.0.1.12 - only one pc
[webhook]
ALLOWED_HOST_LIST = 10.0.1.12

# 10.0.x.x - all pcs in my house
[webhook]
ALLOWED_HOST_LIST = 10.0.0.0/16

# 10.0.1.x - all pcs in my homelab
[webhook]
ALLOWED_HOST_LIST = 10.0.1.0/24

Save the file (CTRL+O and CTRL+X on nano) and start the container again:

docker compose up -d

Once the container starts up, go to ‘yourip:3000’ and you will be greated with an installer, follow the instructions below and change the settings that matter to you. Be carefull to not change the URL or ports, as that can leave your server unaccessible:

Only change the settings that you need
Expand 'Administrator Account Settings'
Create your Admin account and press install
Wait a little until Gitea is ready
Create a new Org
Choose a name and it's visibility
Then go to 'Site Administration'
Identity Access -> User Accounts -> Create User Account
Give jenkins a username, email and password
Click on the edit pencil for jenkins
Check 'Is Administrator'

Security Tips: If you want better security, I recommend using App Credentials instead of User+Password and instead of making the jenkins user an admin, you should add it onto each organization manually.

2 - Setup Jenkins

Create a folder for jenkins with a docker compose file in it:

mkdir jenkins
cd jenkins
nano docker-compose.yml

In this docker compose file we will expose ports 9000 (Webpage) and 50000 (Agent TCP port). Because jenkins recommends pinning a version, we will use the most recent at the time of writing:

services:
  jenkins:
    image: jenkins/jenkins:2.484
    container_name: jenkins
    privileged: true
    user: root
    restart: always
    ports:
      - "9000:8080"
      - "50000:50000"
    volumes:
      - ./data:/var/jenkins_home
      - /home/server/.ssh/:/var/jenkins_home/.ssh/:ro
      - /var/run/docker.sock:/var/run/docker.sock

Once again, start the container by running:

docker compose up

If for some reason, you do not see a password in your console, or you didn’t save it, you can get it back by starting the container with ‘-d’ and running the following command:

docker exec -it jenkins sh

And then, inside the container, run:

cat /var/jenkins_home/secrets/initialAdminPassword

On your browser go to ‘yourip:9000’ to start the setup:

Input the password you got
Install suggested plugins
Wait until completion
Create your Admin user
Set your URL (invalid URL can make Jenkins unaccessible)
Click on 'Start Using Jenkins'
Click on 'Manage Jenkins' and 'Plugins'
Select Gitea
Select Docker Pipeline and install
Go back to the Dashboard and click on 'Setup Agent'
Give it a name and select 'Permanent Agent'
Set the 'Remote root dir' and click 'Save'
Click on your new agent
Don't close this tab yet

On a terminal in your agent (either the PC that is running jenkins or another PC that has docker) install openjdk 21:

sudo apt install openjdk-21-jdk

Create a folder for jenkins (same location you set while creating the agent):

mkdir ~/jenkins
cd ~/jenkins

Inside the jenkins folder, paste the command Jenkins gave you (first one for Unix), check if it connects, then press ‘CTRL+C’ to stop it and run:

sudo nano /etc/systemd/system/jenkins-agent.service

Inside, paste the following config but switch the URL, secret and java and work directories:

[Unit]
Description=Jenkins Agent
After=network.target

[Service]
User=server
WorkingDirectory=/home/server/jenkins
ExecStart=/usr/lib/jvm/java-21-openjdk-amd64/bin/java -jar /home/server/jenkins/agent.jar -url http://yoururl:9000/ -secret yoursecret -name Local -workDir /home/server/jenkins
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Save the file and run:

sudo systemctl daemon-reload
sudo systemctl enable jenkins-agent.service
sudo systemctl start jenkins-agent.service

After a couple seconds, you should see the following page in status (refresh if needed):

3 - Connect to Gitea

Now that we have both Gitea and Jenkins setup and running, we can now, in Jenkins, add our Gitea server:

Go to 'Manage Jenkins' and 'System'
Scroll down until you find 'Gitea Servers' and add a new one
Give it a name and set it's url
Back to the dashboard, click 'New Item'
Give it a name and select 'Organization Folder'
Add Repository Source from 'Gitea Organization'
Select the Gitea Server, and click 'Add' below Credentials
Set the username, password and give it an ID
Select the newly created creds and set the owner to the Org name
You can disable Periodic Organization Folder Triggers, as we will use Webhooks from Gitea to start the task
Once you see SUCCESS, you are ready to create your first repo (don't close this tab yet)

4 - Example

With all this setup done, go to Gitea, create a repo in your Organization and clone it:

We will now need some files in our app repo, one being a Dockerfile that will be built and it is where our app will run on, and the other being the Jenkinsfile. Let’s start with the Dockerfile, we will use Ubuntu 24 and install gcc and build essentials:

# Base img
FROM ubuntu:noble

# Define maintainer
LABEL maintainer=TMVTech 

# Update img with latest packages
RUN apt-get update && apt-get upgrade -y

# Install packages
RUN apt-get install -y gcc build-essential

For this example we will also use a very simple makefile, that compiles our main file:

all: 
	gcc src/main.c -o out/main

Our main file will be placed into a folder named ‘src’ and will be as simple as the makefile that compiles it. Simly print ‘Hello’:

#include <stdio.h>

int main()
{
    printf("Hello From C App\n");
    return 0;
}

Finally we need to create our Jenkinsfile. In this file you can setup all the steps the agent will take to build and test your app, you can even publish it, if all tests pass. A small summary for this Jenkinsfile:

  • ‘pipeline’: the root of our jenkinsfile
  • ‘agent’: which machine will run the steps (in this case, the docker container built from the dockerfile)
  • ‘stages’: a group of stages
  • ‘stage’: a stage is a group of commands (steps) that execute a task (for example, build, test, publish)
  • ‘steps’: what commands to run in that stage (for example, on build stage, run gcc, on test state, run app)

Let’s test our app by pasting the following in the Jenkinsfile:

pipeline 
{
    agent { dockerfile true }
    stages 
    {
        stage ('Build')
        {
            steps
            {
                echo 'Starting Build...'

                // Delete old and create new out folder
                sh 'rm -rf out && mkdir out -p'

                // Check if make is installed
                sh 'make --version'
                
                // Run make to build the C program (assuming Makefile is in the repository)
                sh 'make'   
            }
        }
        stage ('Test')
        {
            steps 
            {
                echo 'Running Test...'

                // Set as executable
                sh 'chmod +x ./out/main'

                // Run
                sh './out/main'
            }
        }
    }
}

With all this ready, make a commit to your repo and go to Jenkins:

On your organization run 'Scan Gitea Organization' and check it's log
If you succeeded, go to your repo's settings and add a webhook
URL: http://yourip:9000/gitea-webhook/post
Go to your hook and 'Test Delivery', should get ✅

Back to your local pc, make some change to the main file, like add a new print or change the message, then create a new commit and watch the magic happen in front of your eyes!

Look at your Build Queue, you should see something, click on it
On the bottom left, click on '#2'
Click on 'Console Output'
You should see the container being built, the app compiling and running

And that’s it! Now every time you push to a branch, a task should run to build and test your code! If you want to, you can also specify which branches should get built, for example, only branches with ‘release-*’ in the name or only main.

But that’s all for this article, thanks for reading until the end and stay tuned for more tech insights and tutorials. Until next time, and keep exploring the world of tech!