Today I will share how to install and setup a GitLab server on FreeBSD. Most people just use Microslopft GitHub these days but this approach has one big drawback – its cloud only solution. When it comes to on premise solutions there are GitLab and there is also Gitea. GitLab is closest to what GitHub provides while Gitea is very light and smaller brother trying to achieve the same goals by doing less. Good to have alternatives. The Table of Contents is more or less like that:Bhyve VMPackagesPostgreSQLRedisGitLabNginxFinal GitLab TweaksGitLab Web InterfaceSummaryWe will use a dedicated Bhyve VM for that – with sysutils/vm-bhyve-devel help.Bhyve VMWe will also use FreeBSD project provided ready to use FreeBSD-15.1-RELEASE-amd64-zfs.raw.xz VM image.host # vm create -t freebsd gitlabhost # env EDITOR=cat vm config gitlabloader="bhyveload"cpu=4memory=16Gnetwork0_type="virtio-net"network0_switch="public"network0_mac="58:9c:fc:01:fb:f8"disk0_type="nvme"disk0_name="disk0.img"uuid="15cd2fff-7942-4ca4-a29f-6ed48f063371"host # xz -d -c FreeBSD-15.1-RELEASE-amd64-zfs.raw.xz > /vm/gitlab/disk0.imghost # truncate -s 30g /vm/gitlab/disk0.imghost # vm start gitlab Starting gitlab * found guest in /vm/gitlab * booting...host # vm console gitlab login: rootroot@gitlab:~ # :> ~/.hushloginroot@gitlab:~ # gpart show -p=> 34 62914486 nda0 GPT (30G) 34 347 nda0p1 freebsd-boot (174K) 381 66584 nda0p2 efi (33M) 66965 2097152 nda0p3 freebsd-swap (1.0G) 2164117 60750403 nda0p4 freebsd-zfs (29G)To expand the disk/partition/pool this is the guide You are looking for – Expand GELI Encrypted Bhyve VM ZFS Disk – just omit the GELI part as its not used here.Back to the VM configuration.root@gitlab:~ # cat /etc/rc.conf# NETWORK hostname="gitlab.local" ifconfig_vtnet0="inet 10.1.1.44/24 up" defaultrouter="10.1.1.1"# DAEMONS zfs_enable="YES"root@gitlab:~ # sed -i '' s/quarterly/latest/g /etc/pkg/FreeBSD.confroot@gitlab:~ # echo nameserver 1.1.1.1 > /etc/resolv.confroot@gitlab:~ # netstat -Win -f inetName Mtu Network Address Ipkts Ierrs Idrop Opkts Oerrs Collvtnet0 - 10.1.1.0/24 10.1.1.44 905 - - 466 - -lo0 - 127.0.0.0/8 127.0.0.1 633774 - - 633774 - -PackagesNeeded packages installation.root@gitlab:~ # \ pkg install -y \ gitlab-ce \ postgresql18-server \ postgresql18-client \ postgresql18-contrib \ nginx \ redisPostgreSQLNow PostgreSQL database setup.root@gitlab:~ # service postgresql enableroot@gitlab:~ # service postgresql initdbroot@gitlab:~ # service postgresql startroot@gitlab:~ # psql -d template1 -U postgres -c "CREATE USER git CREATEDB SUPERUSER PASSWORD 'password';"root@gitlab:~ # psql -d template1 -U postgres -c "CREATE DATABASE gitlabhq_production OWNER git;"Now lets try to connect to new database with new user.root@gitlab:~ # psql -U git -d gitlabhq_productionpsql (18.4)Type "help" for help.gitlabhq_production=# Seems to work.Now connect to PostgreSQL GitLab database and enable pg_trgm extension.root@gitlab:~ # psql -U postgres -d gitlabhq_production -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"root@gitlab:~ # psql -U postgres -d gitlabhq_production -c "CREATE EXTENSION IF NOT EXISTS btree_gist;"root@gitlab:~ # psql -U postgres -d gitlabhq_production -c "CREATE EXTENSION IF NOT EXISTS plpgsql;"Next enable 200 connections to PostgreSQL database.root@gitlab:~ # psql -U postgres -c "ALTER SYSTEM SET max_connections TO '200';"root@gitlab:~ # psql -U postgres -c "SELECT pg_reload_conf();"root@gitlab:~ # service postgresql restartVerify it is now set to 200.root@gitlab:~ # psql -U postgres -c "show max_connections;" max_connections ----------------- 200(1 row)RedisSetup Redis config.root@gitlab:~ # echo 'unixsocket /var/run/redis/redis.sock' >> /usr/local/etc/redis.confGrant permission to socket to all members of the redis group.root@gitlab:~ # echo 'unixsocketperm 770' >> /usr/local/etc/redis.confAdd git user to redis group.root@gitlab:~ # pw groupmod redis -m gitAllow Redis to be started and start it.root@gitlab:~ # service redis enableroot@gitlab:~ # service redis startGitLabFind number of CPU cores.root@gitlab:~ # sysctl hw.ncpuhw.ncpu: 4Change amount of workers to 4 next.root@gitlab:~ # grep '^workers' /usr/local/www/gitlab/config/puma.rbworkers 4Configure Git global settings for git user.root@gitlab:~ # su -l git -c "git config --global core.autocrlf input"Disable 'git gc --auto' because GitLab already runs 'git gc' when needed.root@gitlab:~ # su -l git -c "git config --global gc.auto 0"Enable packfile bitmaps.root@gitlab:~ # su -l git -c "git config --global repack.writeBitmaps true"Enable push options.root@gitlab:~ # su -l git -c "git config --global receive.advertisePushOptions true"Enable fsyncObjectFiles to reduce risk of repository corruption if server crashes.root@gitlab:~ # su -l git -c "git config --global core.fsync objects,derived-metadata,reference"Make sure SSH directory exists for GitLab user.root@gitlab:~ # su -l git -c "mkdir -p /usr/local/git/.ssh"Make sure repositories directory exists with correct permissions.root@gitlab:~ # su -l git -c "mkdir -p /usr/local/git/repositories"root@gitlab:~ # chown git:git /usr/local/git/repositoriesroot@gitlab:~ # chmod 2770 /usr/local/git/repositoriesChange passwords.root@gitlab:~ # sed -i '' -e 's/secure password/password/g' /usr/local/www/gitlab/config/database.ymlShow used passwords.root@gitlab:~ # grep password: /usr/local/www/gitlab/config/database.yml password: "password" password: "password" password: "password" password: "password" password: "password" password: "password" password: password:Proper owner for GitLab shell.root@gitlab:~ # chown git /usr/local/share/gitlab-shellSetup GitLab with its password.root@gitlab:~ # su -l git -c "cd /usr/local/www/gitlab && env DISABLE_DATABASE_ENVIRONMENT_CHECK=1 rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=password"(...)Do you want to continue (yes/no)? yes(...)Could not create the default administrator account:--> Password must not contain commonly used combinations of words and lettersI wanted this guide to be a generic setup – with ‘must to change’ password as password but GitLab knows better – lets cope with that.Lets try something more sophisticated:root@gitlab:~ # su -l git -c "cd /usr/local/www/gitlab && env DISABLE_DATABASE_ENVIRONMENT_CHECK=1 rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=GtFO/wTh!sBu112!t"(...)Do you want to continue (yes/no)? yesDropped database 'gitlabhq_production'Created database 'gitlabhq_production'== Seed from /usr/local/www/gitlab/db/fixtures/production/001_application_settings.rbCreating the default ApplicationSetting record.== /usr/local/www/gitlab/db/fixtures/production/001_application_settings.rb took 0.70 seconds== Seed from /usr/local/www/gitlab/db/fixtures/production/002_default_organization.rbOK== /usr/local/www/gitlab/db/fixtures/production/002_default_organization.rb took 0.08 seconds== Seed from /usr/local/www/gitlab/db/fixtures/production/003_admin.rbAdministrator account created:login: rootpassword: ******== /usr/local/www/gitlab/db/fixtures/production/003_admin.rb took 2.61 seconds== Seed from /usr/local/www/gitlab/db/fixtures/production/010_settings.rbSaved CI JWT signing keySaved CI Job Token signing key== /usr/local/www/gitlab/db/fixtures/production/010_settings.rb took 0.67 seconds== Seeding took 4.06 secondsroot@gitlab:~ # echo $?0root@gitlab:~ # su -l git -c "cd /usr/local/www/gitlab && rake gitlab:env:info RAILS_ENV=production"you have mailSystem informationSystem: Current User: gitUsing RVM: noRuby Version: 3.4.9Gem Version: 4.0.15Bundler Version:4.0.15Rake Version: 13.4.2Redis Version: 8.8.0Sidekiq Version:7.3.9Go Version: unknownGitLab informationVersion: 19.1.1Revision: UnknownDirectory: /usr/local/www/gitlabDB Adapter: PostgreSQLDB Version: 18.4URL: http://localhostHTTP Clone URL: http://localhost/some-group/some-project.gitSSH Clone URL: git@localhost:some-group/some-project.gitUsing LDAP: noUsing Omniauth: yesOmniauth Providers: GitLab ShellVersion: 14.54.0Repository storages:- default: unix:/usr/local/www/gitlab/tmp/sockets/private/gitaly.socketGitLab Shell path: /usr/local/share/gitlab-shell(...)The terminal output is much more colorful. Better.Now lets start it.root@gitlab:~ # su -l git -c "cd /usr/local/www/gitlab && rake gitlab:env:info RAILS_ENV=production"Compile needed assets … it will take some time.If something breaks … just run it again – should help.root@gitlab:~ # su -l git -c "cd /usr/local/www/gitlab && yarn config set python /usr/local/bin/python3.12"root@gitlab:~ # su -l git -c "cd /usr/local/www/gitlab && yarn install --production --pure-lockfile --ignore-optional"root@gitlab:~ # su -l git -c "cd /usr/local/www/gitlab && node scripts/frontend/infection_scanner/infection_scanner.mjs"root@gitlab:~ # su -l git -c "cd /usr/local/www/gitlab && RAILS_ENV=production NODE_ENV=production USE_DB=false SKIP_STORAGE_VALIDATION=true bundle exec rake gitlab:assets:compile"Remove superuser rights from PostgreSQL database user.root@gitlab:~ # psql -d template1 -U postgres -c "ALTER USER git WITH NOSUPERUSER;"… and enable and start GitLab service.root@gitlab:~ # service gitlab enableroot@gitlab:~ # service gitlab startGitLab Registry Key missing. Generating...(...)Starting GitLab web server (puma)Starting GitLab SidekiqStarting GitLab WorkhorseStarting Gitaly(...)Started in 53s.The GitLab web server with pid 70182 is running.The GitLab Sidekiq job dispatcher with pid 72791 is running.The GitLab Workhorse with pid 74932 is running.Gitaly with pid 76120 is running.GitLab and all its components are up and running.NginxSite Configuration – include provided configuration in your Nginx configuration.root@gitlab:~ # vi /usr/local/etc/nginx/nginx.confWithin 'http' configuration block add this:include /usr/local/www/gitlab/lib/support/nginx/gitlab;Looks more or less like this.root@gitlab:~ # diff -u /usr/local/etc/nginx/nginx.conf.ORG /usr/local/etc/nginx/nginx.conf--- /usr/local/etc/nginx/nginx.conf.ORG 2026-07-07 09:46:08.835333000 +0000+++ /usr/local/etc/nginx/nginx.conf 2026-07-07 09:46:32.884929000 +0000@@ -37,6 +37,8 @@ #gzip on; + include /usr/local/www/gitlab/lib/support/nginx/gitlab;+ server { listen 80; server_name localhost;Validate Nginx config file.root@gitlab:~ # nginx -tnginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is oknginx: configuration file /usr/local/etc/nginx/nginx.conf test is successfulThis above is the message You expect to see.Next enable and start Nginx server.root@gitlab:~ # service nginx enableroot@gitlab:~ # service nginx restartFinal GitLab TweaksNext create GitLab secret.root@gitlab:~ # su -l git -c "openssl rand -base64 32 > /usr/local/www/gitlab/.gitlab_pages_secret"root@gitlab:~ # chmod 640 /usr/local/www/gitlab/.gitlab_pages_secretroot@gitlab:~ # chgrp gitlab-pages /usr/local/www/gitlab/.gitlab_pages_secretroot@gitlab:~ # cat /usr/local/www/gitlab/.gitlab_pages_secretp+IUsbCnDrpltlyPGq8gBzrHIkKN3++/CD+4C76Wlnw=Lets check who is listening and on which port.root@gitlab:~ # sockstat -l4USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS www nginx 15826 8 tcp4 *:80 *:* root nginx 15825 8 tcp4 *:80 *:* git ruby34 2224 11 tcp4 127.0.0.1:8082 *:* git ruby34 2223 16 tcp4 127.0.0.1:8092 *:* redis redis-serv 1143 6 tcp4 127.0.0.1:6379 *:* postgres postgres 1084 7 tcp4 127.0.0.1:5432 *:* root syslogd 914 7 udp4 *:514 GitLab Web InterfaceWe can now access the GitLab web interface finally.Login with root user and GtFO/wTh!sBu112!t password. You can just click Skip here and cover it later. Same here. There are some Acknowledgements here to be taken care of. Seems its all ready. You now have your GitLab instance ready to be used for whatever you want to use it for.The final /etc/rc.conf config file after all these actions looks as follows.root@gitlab:~ # cat /etc/rc.conf# NETWORK hostname="gitlab.local" ifconfig_vtnet0="inet 10.1.1.44/24 up" defaultrouter="10.1.1.1"# DAEMONS zfs_enable="YES" postgresql_enable="YES" redis_enable="YES" gitlab_enable="YES" nginx_enable="YES"SummaryI assume You will want to add some certificate to Nginx config to make it HTTPS way … and that is a good thing to start.Not much more to be added here – feel free to share Your thoughts of course. EOF