Post

ENG | Setting up Gitea (or Forgejo)

Discover a comprehensive guide to self-hosting Gitea, a lightweight DevOps platform for Git hosting. This article walks you through the installation, setup, and operation of Gitea using Docker Compose, including configuring user registration, securing remote HTTPS access with Cloudflare tunnels, and automating startup with systemd. Explore practical examples of creating new repositories, migrating existing ones, and implementing backup scripts for data recovery. Some sections are specific to Podman.

ENG | Setting up Gitea (or Forgejo)

Introduction

The purpose of this article is to provide a detailed guide on the installation, setup, and operation of Gitea, a lightweight DevOps platform for Git hosting. Gitea represents an efficient solution for those desiring to establish a collaborative environment for team projects or seeking to create a private repository for personal development efforts.

Maybe take a look at Forgejo which is fork of Gitea and as of 2024 they seem more active, or at least open about new features. As of June 2024 these softwares are really similar and official docker image even has the same structure and directory names. However Docker compose file differs a bit (by image name and environment variables.)

Bear in mind, it will be helpful if you’ve followed previous articles on Cloudflare tunnels and setting up Nextcloud using podman. These will provide you with a foundational understanding that will aid you in this Gitea setup guide.

Let’s start with docker compose

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# Based on https://docs.gitea.com/installation/install-with-docker-rootless
# and my other docker-compose.yml files
# Changelog:
# 2023-07-03: Initial version
# 2024-06-30: Forgejo mentioned in comments

version: "3"

volumes:
  gitea-data:
  gitea-config:
  gitea-db:

services:

  gitea-db:
    image: lscr.io/linuxserver/mariadb:latest
    container_name: gitea-mariadb
    restart: unless-stopped
    environment:
      - PUID=1000
      - TZ=${TIMEZONE}
      - MYSQL_DATABASE=${GITEA_DB_NAME}
      - MYSQL_USER=${GITEA_DB_USER}
      - MYSQL_PASSWORD=${GITEA_DB_PASS}
      - MYSQL_ROOT_PASSWORD=${GITEA_DB_PASS}
    volumes:
      - gitea-db:/config

  gitea-server:
    # Rewrite to actual version of Gitea
    # Alternatively, change to Forgejo:
    # image: codeberg.org/forgejo/forgejo:7.0-rootless
    image: docker.io/gitea/gitea:1.21-rootless
    container_name: gitea
    restart: always
    environment:
        # Possibly rename GITEA__database to FORGEJO__database
      - GITEA__database__DB_TYPE=mysql
      - GITEA__database__HOST=gitea-db:3306
      - GITEA__database__NAME=${GITEA_DB_NAME}
      - GITEA__database__USER=${GITEA_DB_USER}
      - GITEA__database__PASSWD=${GITEA_DB_PASS}
    volumes:
      # File not present on Fedora
      #- /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
      - gitea-data:/var/lib/gitea
      - gitea-config:/etc/gitea
    ports:
      - "8082:3000"
      - "2222:2222"
    depends_on:
      - gitea-db

There is nothing secret in ~/docker/.env file.

1
2
3
4
5
6
TIMEZONE=Europe/Prague
GITEA_DB_TYPE=mysql
GITEA_DB_NAME=gitea
GITEA_DB_USER=gitea
GITEA_DB_PASS=gitea
GITEA_DB_HOST=gitea-db

Initial setup

By default users are automatically registered and first one becomes admin. Password must be 8 characters long. Settings in yellow are changed from defaults. Later they can be changed by editing config file.

Denying user auto-registration

Editing file inside Alpine Linux container without editor

This chapter can be skipped. There’s a vi inside container. Left for learning purposes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[pavel@marten -=- /home/pavel]$ podman exec -it gitea sh
/var/lib/gitea $ cd /etc/gitea
/etc/gitea $ ls -la
total 4
drwx------    1 git      git             14 Jul  3 21:16 .
drwxr-xr-x    1 root     root            58 Jul  3 21:07 ..
-rw-r--r--    1 git      git           2413 Jul  3 21:09 app.ini
/etc/gitea $ vim app.ini
sh: vim: not found
/etc/gitea $ nano app.ini
sh: nano: not found
/etc/gitea $ apk add nano
ERROR: Unable to lock database: Permission denied
ERROR: Failed to open apk database: Permission denied
/etc/gitea $ id
uid=1000(git) gid=1000(git)

Aha. Now we have two solutions. From config files we know how this volume is mounted and edit app.ini this way

1
podman unshare vim /home/pavel/.local/share/containers/storage/volumes/docker_gitea-config/_data/app.ini

or other option is

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[pavel@marten -=- /home/pavel]$ podman exec --user=root -it gitea sh
/var/lib/gitea # id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
/var/lib/gitea # apk add nano
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/community/x86_64/APKINDEX.tar.gz
(1/1) Installing nano (7.0-r0)
Executing busybox-1.35.0-r29.trigger
OK: 36 MiB in 63 packages
/var/lib/gitea # cd /etc/gitea
/etc/gitea # ls
app.ini
/etc/gitea # nano app.ini
/etc/gitea # exit

Editing config

See Gitea cheatsheet and edit /etc/gitea/app.ini accordingly.

1
podman unshare vim /home/pavel/.local/share/containers/storage/volumes/docker_gitea-config/_data/app.ini

DISABLE_REGISTRATION: false: Disable registration, after which only admin can create accounts for users.

Now restart Gitea

1
podman-compose -f ~/docker/docker-compose-gitea.yml up -d

Add Gitea to Cloudflare tunnel for remote HTTPS access

See the [Cloudflare post]/posts/eng-cloudflare/)

Tunnel settings are:

1
2
3
4
5
Subdomain: git
Domain:    pavelp.cz
Path:      
Type:      HTTP
URL:       gitea:3000

This redirects https://git.pavelp.cz/ to http://gitea:3000/

Add systemd service for automatic start

See Nextcloud config for this

Create first repository (a new one)

Use web interface to create repository (e.g. docker-files) and follow the instructions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
[pavel@marten -=- /home/pavel/docker]$ git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint:   git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint:   git branch -m <name>
Initialized empty Git repository in /home/pavel/docker/.git/


[pavel@marten -=- /home/pavel/docker]$ git checkout -b main
Switched to a new branch 'main'


[pavel@marten -=- /home/pavel/docker]$ git status
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .gitignore
        docker-compose-gitea.yml
        docker-compose-old.tar.xz
        docker-compose.yml

nothing added to commit but untracked files present (use "git add" to track)


[pavel@marten -=- /home/pavel/docker]$ git add .


[pavel@marten -=- /home/pavel/docker]$ git commit -m "initial commit"
[main (root-commit) edf09d1] initial commit
 4 files changed, 172 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 docker-compose-gitea.yml
 create mode 100644 docker-compose-old.tar.xz
 create mode 100644 docker-compose.yml


[pavel@marten -=- /home/pavel/docker]$ git remote add origin https://git.pavelp.cz/pavel.perina/docker-files.git


[pavel@marten -=- /home/pavel/docker]$ git config --global --list
[email protected]
user.name=Pavel Perina


[pavel@marten -=- /home/pavel/docker]$ git push -u origin main
Username for 'https://git.pavelp.cz': pavel.perina
Password for 'https://[email protected]':
Enumerating objects: 6, done.
Counting objects: 100% (6/6), done.
Delta compression using up to 4 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 4.56 KiB | 4.56 MiB/s, done.
Total 6 (delta 0), reused 0 (delta 0), pack-reused 0
remote: . Processing 1 references
remote: Processed 1 references in total
To https://git.pavelp.cz/pavel.perina/docker-files.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

Note git push -u is equal to git push --set-upstream.

Create second repository (from existing)

Difference here is changing origin, because this repository was pulled from github. Technically github can be set as another upstream repository.

There was attempt to push all branches, but there is surprisingly only one. I fetched tag 6.0.1 if I recall and created branch pavel instantly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[pavel@marten -=- /home/pavel/dev-blog]$ git status
On branch pavel
nothing to commit, working tree clean

[pavel@marten -=- /home/pavel/dev-blog]$ git remote -v
origin  https://github.com/cotes2020/jekyll-theme-chirpy.git (fetch)
origin  https://github.com/cotes2020/jekyll-theme-chirpy.git (push)

[pavel@marten -=- /home/pavel/dev-blog]$ git remote set-url origin https://git.pavelp.cz/pavel.perina/blog.git

[pavel@marten -=- /home/pavel/dev-blog]$ git remote -v
origin  https://git.pavelp.cz/pavel.perina/blog.git (fetch)
origin  https://git.pavelp.cz/pavel.perina/blog.git (push)

[pavel@marten -=- /home/pavel/dev-blog]$ git push --all origin
Username for 'https://git.pavelp.cz': pavel.perina
Password for 'https://[email protected]':
Enumerating objects: 8943, done.
Counting objects: 100% (8943/8943), done.
Delta compression using up to 4 threads
Compressing objects: 100% (3987/3987), done.
Writing objects: 100% (8943/8943), 4.87 MiB | 2.55 MiB/s, done.
Total 8943 (delta 4913), reused 8781 (delta 4828), pack-reused 0
remote: Resolving deltas: 100% (4913/4913), done.
remote: . Processing 1 references
remote: Processed 1 references in total
To https://git.pavelp.cz/pavel.perina/blog.git
 * [new branch]      pavel -> pavel

Backup script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/usr/bin/dash
#
# Pavel Perina
#
# Changes:
# * 2023-07-04 Initial version (based on backup-nextcloud.sh)
# * 2023-07-17 Increase compression
# * 2024-06-28 Added alternative db backup

#######################
# Setup variables
. $HOME/docker/.env
DATE=$(date +%Y-%m-%d)
TARGET=$HOME/backup

#podman volume inspect docker_gitea-db \
#  || podman volume create docker_gitea-db \
#  && zstd -d | podman volume import docker_gitea-db
#######################
# Backup Gitea

# https://docs.gitea.com/next/administration/backup-and-restore

backup_gitea() {
        cd ~/docker

        echo "🛑 Shutting down Gitea ..."
        podman-compose -f docker-compose-gitea.yml down

        echo "💾 Backing up Gitea config ..."
        podman volume export docker_gitea-config | zstd -9 > $TARGET/gitea-config-$DATE.tar.zst 

        echo "💾 Backing up Gitea data files..."
        podman volume export docker_gitea-data  | zstd -11 > $TARGET/gitea-data-$DATE.tar.zst

        echo "💾 Backing up Gitea database (be patient) ..."
        podman volume export docker_gitea-db  | zstd -11 > $TARGET/gitea-db-$DATE.tar.zst
        # Alternatively backup database with user, password, and name gitea from container gitea-mariadb
        #podman exec gitea-mariadb /usr/bin/mysqldump --user=gitea --password=gitea gitea | zstd -11 gitea-sqldump-$DATE.sql.zst

        echo "🟢 Bringing Gitea up ..."
        podman-compose -f docker-compose-gitea.yml up -d

        echo "Gitea backup finished"
}

backup_gitea

Data recovery

See Backup and disaster recovery plan which demonstrates how to recover data or migrate them to another server.

Addendum

Initial Git Configuration

It’s good to have some default git config such as

1
2
3
4
5
[user]
        email = [email protected]
        name = Your Name
[init]
        defaultBranch = main

On Windows this file is in C:\Users\your.login\.gitconfig. It can be created by the following commands:

1
2
3
git config --global user.name "Your Name"
git config --global user.email "[email protected]"
git config --global init.defaultBranch main

You can change settings for your work projects (you probably want to use different email) which where config is stored in .git/config inside repository:

1
2
git config --local user.email [email protected]
git submodule foreach "git config --local user.email [email protected]"

Now let’s add default user for our repo:

1
2
3
4
5
[pavel@marten -=- /home/pavel/dev-blog]$ git push
Username for 'https://git.pavelp.cz': ^C
[pavel@marten -=- /home/pavel/dev-blog]$ git config --global credential.https://git.pavelp.cz.username pavel.perina
[pavel@marten -=- /home/pavel/dev-blog]$ git push
Password for 'https://[email protected]':

Ok. Now I tried https://www.techaddressed.com/tutorials/add-verify-ssh-keys-gitea/, but then I realized I must use https login.

Changelog

  • 2024-06-30: Forgejo mentioned, default username, link.
  • 2024-09-12: Initial configuration updated.
This post is licensed under CC BY 4.0 by the author.