Gitea – Self-Hosted Git & CI/CD Tools

This article is about my deployment experience of Gitea, a self-hosted GitHub like code hosting service which also support CI/CD pipeline workflows like GitHub Action.

Why I Need It

Recently, I was searching for a way to host Obsidian vault on the Internet. There is an official way to achieve this using Obsidian Publish, however, it’s not free.

So, I keep searching the Internet and finally saw this Reddit post discussing using SSG (static site generator) to host Obsidian vault, that’s exactly what I want. I randomly pick one in the comments section called Quartz, tried it out on my local computer, and seems that it works well with Obsidian.

Now the only thing I need to do is to have a self-host deployment tool like GitHub actions to automatically deploy my code every time I update my content. And my basic thought is shown by the image below:

Installation

Write at the beginning, it’s highly recommended to follow the official installation guide to finish your docker installation.

Gitea Installation

Installation Gitea using docker is quite straightforward, just follow its official guide. I choose to use docker compose method to create the container.

  1. Create a new unprivileged user gitea (or any other name you want)
  2. Create a new folder, here I use the gitea user folder
  3. Write your docker-compose.yml inside the folder
  4. Run docker compose up -d

In linux, we could create user using following command:

sudo adduser gitea

After create the user, we could go to its home directory /home/gitea and create a new docker-compose.yml file. Below is my own one for Gitea docker container. For more info, please read Official Gitea Docker Installation Guide on their website.

version: "3"

networks:
  gitea:
    external: false

services:
  server:
    image: docker.gitea.com/gitea:latest
    container_name: gitea
    environment:
      - USER=gitea
      - USER_UID=1005
      - USER_GID=1005
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=db:5432
      - GITEA__database__NAME=gitea
      - GITEA__database__USER=gitea
      - GITEA__database__PASSWD=gitea
    restart: always
    networks:
      - gitea
    volumes:
      - /sto/gitea:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "30015:3000"
      - "30016:22"
    depends_on:
      - db

  db:
    image: docker.io/library/postgres:14
    restart: always
    environment:
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=gitea
      - POSTGRES_DB=gitea
    networks:
      - gitea
    volumes:
      - ./postgres:/var/lib/postgresql/data

Action Runner Installation

Finishing Gitea installation isn’t an end: The action feature doesn’t come with Gitea docker image installation. For the feature I depicted above to work, we also need to configure a runner to execute actions defined in our repository.

Still, Gitea have a well-documented official Gitea Act Runner guide for this procedure, please check it out.

Cache Action Support

One thing to notice in this guide is that if we want to use actions/cache in our workflow, then the procedures in “Advanced Configurations” is needed in order to make cache action work.

Error When Not Configured Correctly

For demonstration, I will put on my own docker-compose.yml and config.yml for Act Runner:

services:
  runner:
    image: docker.io/gitea/act_runner:latest
    ports:
      - "30017:30017"
    environment:
      CONFIG_FILE: /config.yaml
      GITEA_INSTANCE_URL: "https://my-host.example.com"
      GITEA_RUNNER_REGISTRATION_TOKEN: "<my_reg_token>"
      GITEA_RUNNER_NAME: "example-runner"
      GITEA_RUNNER_LABELS: ""
    volumes:
      - ./config.yaml:/config.yaml
      - /var/run/docker.sock:/var/run/docker.sock
      - ./runner_output:/runner_output
    restart: always
cache:
  enabled: true
  dir: ""
  host: "<my_host_public_ipv4_addr>"
  port: 30017

Note that mapped port is the same, just like above we have mapping "30017:30017".

Also, if we configured act-runner and the temporary job runner to use the same docker bridge network (checkout Enable IPv6 chapter below), then we don’t even need to publish the port of act runner container. In this case, we first check the LAN IP address act-runner container is using, e.g. 172.0.20.1, then we use this IP as host and arbitrary port, e.g. 8088 then everything should work well.

Use LAN IP when in same bridge network

As shown below, cache should work once configured correctly:

Chace Worked Now

Note that we should NOT set GITEA_RUNNER_EPHEMERAL=1 environmental variable, otherwise the container will stop immediately after finishing any distributed action task.

Enable IPv6 For Action Runner

The default bridge network does NOT enable IPv6, so we need to manually enable it in our docker-compose.yml file:

// previous configuration
networks:
  act_runner:
    name: act_runner_net
    driver: bridge
    enable_ipv6: true

The full Action Runner docker compose file looks like the one below:

services:
  runner:
    container_name: gitea-act-runner
    image: docker.io/gitea/act_runner:latest
    ports:
      - "30017:30017"
    environment:
      CONFIG_FILE: /config.yaml
      GITEA_INSTANCE_URL: "https://gitea.example.com/"
      GITEA_RUNNER_REGISTRATION_TOKEN: "TOKEN_SECRET"
      GITEA_RUNNER_NAME: "gc-4t"
      GITEA_RUNNER_LABELS: ""
    volumes:
      - ./config.yaml:/config.yaml
      - /var/run/docker.sock:/var/run/docker.sock
      - ./runner_output:/runner_output
    restart: always
    networks:
      - act_runner

networks:
  act_runner:
    name: act_runner_net
    driver: bridge
    enable_ipv6: true

However, this is not enough, since every time an action task has been distributed to the runner, the runner will create a temporary docker container. We need to use config.yml to ensure such temporary container also use this network.

We could write config.yml like below:

cache:
  enabled: true
  dir: ""
  host: "IP_ADDR"
  port: YOUR_PORT

container:
  network: "act_runner_net"

This will ensure every newly created temporary container use act_runner_net as its network. About details of Act Runner config file, checkout example config file.

Example Config File
FTP action new work with IPv6

For more info about IPv6 network question, checkout this community question.

For info above docker network, checkout official docs of Networking and Bridge network.

Maintenance

About Act Runner Cache

If we do not customize the cache storage path for Act Runner, the cache files will actually store in the main act runner docker container’s root user path:

/root/.cache/act
/root/.cache/actcache

Since the cache could be accumulated to a large size in the future, it’s better to know where it is and manually clean it if needed.

Enjoy Self-Hosted Actions

After all the steps above, the Gitea should now run on the host. And it should be able to execute actions defined in the .github/workflows directory of your project:

As shown above, I used this self-host Gitea to auto-build my Leetcode note website codenotes.nfblogs.com, every time I push to this remote repo, Gitea will automatically run npx quartz build to generate public folder and then use FTP to deploy to target directory on my web server.

Use LFS With actions/checkout

Currently, the action/checkout doesn’t work well with Git LFS storage, and will fail to fetch LFS files when using lfs:true, as shown in the image below.

Such bugs are detailed discussed in this GitHub Issue page. Luckily, there is a workaround for actions/checkout@v6 shown in the discussion:

git lfs install --local
AUTH=$(git config http.${{ gitea.server_url }}/.extraheader)
AUTH_FILE=$(git config includeif.gitdir:/workspace/${{ gitea.repository }}/.git.path)
git config -f $AUTH_FILE --unset http.${{ gitea.server_url }}/.extraheader
git config -f $AUTH_FILE http.${{ gitea.server_url }}/${{ gitea.repository }}.git/info/lfs/objects/batch.extraheader "$AUTH"
git lfs pull

Instead of using lfs:true with checkout action, we could directly use the commands shown above as the step to checkout LFS storage.

# something else

jobs:
  build-and-deploy:
    # ...
    steps:
      - name: Checkout code
        uses: actions/checkout@v6
        with:
          fetch-depth: 1
          # lfs: true  # THIS DOESN'T WORK!!!!!!!!!

      - name: Set up Git LFS
        run: |
          git lfs install --local
          AUTH=$(git config http.${{ github.server_url }}/.extraheader)
          AUTH_FILE=$(git config includeif.gitdir:/workspace/${{ github.repository }}/.git.path)
          git config -f $AUTH_FILE --unset http.${{ github.server_url }}/.extraheader
          git config -f $AUTH_FILE http.${{ github.server_url }}/${{ github.repository }}.git/info/lfs/objects/batch.extraheader "$AUTH"
          git lfs pull

Things That Do Not Resolved

Better Deployment Method

For me, since these are for personal use only, the Act Runner, Web Server and Gitea Server are actually running at the same remote server. So, using FTP to connect from Act Runner Docker to my host seems a little bit wired and unnecessary. Maybe I should find a better way to deploy the artifact public folder after action successfully generate it using npm quartz build.

This part serves as a reminder to myself, maybe I will go back to check and fix these things in the future.

Published by Oyasumi

Just a normal person in the world

2 thoughts on “Gitea – Self-Hosted Git & CI/CD Tools

Leave a Reply

Index