Deploying GitLab CE on Fedora Server: Building Your Own Internal Source Control and CI/CD Platform with SELinux and firewalld

Fedora tutorial - IT technology blog
Fedora tutorial - IT technology blog

Background: When GitHub Isn’t Enough for an Internal Team

Our team has 5 developers and 2 sysadmins — small, but large enough that GitHub Free was becoming a real problem. CI minutes on private repos ran out faster than expected. Some internal projects simply couldn’t have their code pushed to the cloud. And when we tried self-hosting GitLab Runner, build times dropped noticeably — no more depending on upstream bandwidth to GitHub’s servers.

The reason I chose Fedora Server is pretty personal — I’d been using Fedora desktop for 2 years and was comfortable with it. Packages update faster than CentOS or Rocky Linux, the kernel is newer, and SELinux policies get patched more frequently. GitLab CE runs great on Fedora, as long as you don’t do what most guides suggest: disabling SELinux and firewalld to make setup quicker.

Minimum specs: 4GB RAM, 2 CPUs, 20GB disk. But that’s “minimum” in the literal sense — GitLab at idle already consumes around 2.5–3GB RAM. Planning to run GitLab Runner on the same machine? Get 8GB to be safe.

Installing GitLab CE

Preparing the System

Update the system and install the required dependencies first:

sudo dnf update -y
sudo dnf install -y curl policycoreutils openssh-server openssh-clients postfix
sudo systemctl enable --now sshd postfix

Installing GitLab CE from the Official Repository

GitLab provides an automatic repo setup script, after which you install the package normally via DNF:

curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | sudo bash
sudo EXTERNAL_URL="http://gitlab.yourdomain.com" dnf install -y gitlab-ce

Replace gitlab.yourdomain.com with your actual domain or IP. For internal use with a static IP, http://192.168.1.100 works fine. After installation, GitLab automatically runs gitlab-ctl reconfigure for the first time — this takes about 3–5 minutes, which is completely normal.

Detailed Configuration

Handling SELinux — Don’t Disable It

This is the biggest difference from most guides I’ve read. Many articles tell you to run setenforce 0 or set SELINUX=disabled in /etc/selinux/config for a quick fix. I don’t do that — disabling SELinux on a production server throws away an entire layer of protection just to save a few minutes of setup. GitLab CE actually only needs a few booleans enabled:

# Allow GitLab to make network connections (needed for webhooks, email)
sudo setsebool -P httpd_can_network_connect on
sudo setsebool -P httpd_can_network_relay on

# Fix file context for GitLab data directory
sudo semanage fcontext -a -t gitlab_shell_t "/var/opt/gitlab(/.*)?" 
sudo restorecon -Rv /var/opt/gitlab/

# Check if SELinux is blocking anything
sudo ausearch -m avc -ts recent | grep gitlab

If there are still SELinux errors after enabling the booleans, read the audit log and create a custom policy:

sudo ausearch -m avc -ts recent | audit2allow -M gitlab-custom
sudo semodule -i gitlab-custom.pp

This approach is much safer than disabling SELinux — it only permits the exact operations GitLab needs. As a bonus, reading the audit logs gives you insight into what GitLab is actually doing under the hood, which is invaluable when troubleshooting later.

Configuring firewalld

GitLab needs HTTP (80) and HTTPS (443). SSH cloning shares port 22 with the system SSH, so no additional port needs to be opened:

sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

# Verify the configuration
sudo firewall-cmd --list-all

If GitLab runs on a custom port (e.g., 8080), add the corresponding rule:

sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload

Tuning gitlab.rb

The main configuration file is /etc/gitlab/gitlab.rb. Here are the settings I use in production:

# /etc/gitlab/gitlab.rb

# Main URL — must be correct, use HTTPS if you have SSL
external_url 'https://gitlab.yourdomain.com'

# SMTP configuration (Gmail example)
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.gmail.com"
gitlab_rails['smtp_port'] = 587
gitlab_rails['smtp_user_name'] = "[email protected]"
gitlab_rails['smtp_password'] = "app-password-here"
gitlab_rails['smtp_domain'] = "gmail.com"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = true

# Limit RAM for Puma web server
puma['worker_processes'] = 2
puma['min_threads'] = 1
puma['max_threads'] = 4

# Reduce Sidekiq workers to save RAM
sidekiq['concurrency'] = 10

# Keep backups for 7 days
gitlab_rails['backup_keep_time'] = 604800

Apply the changes:

sudo gitlab-ctl reconfigure

Enabling HTTPS with Let’s Encrypt

GitLab CE has Let’s Encrypt built in. Add this to gitlab.rb:

letsencrypt['enable'] = true
letsencrypt['contact_emails'] = ['[email protected]']
letsencrypt['auto_renew'] = true
letsencrypt['auto_renew_hour'] = 3
letsencrypt['auto_renew_day_of_month'] = "*/7"
sudo gitlab-ctl reconfigure
sudo gitlab-ctl renew-le-certs

Verification and Monitoring

First Login

Get the temporary root password (valid for 24 hours):

sudo cat /etc/gitlab/initial_root_password

Log in at http://your-gitlab-url with the root account and that password, then change it immediately. Don’t leave it until tomorrow.

Checking Service Status

sudo gitlab-ctl status

Normal output looks like this — all services should be in the run state:

run: alertmanager: (pid 1234) 120s
run: gitaly: (pid 1236) 120s
run: nginx: (pid 1240) 120s
run: postgresql: (pid 1242) 120s
run: redis: (pid 1244) 120s
run: sidekiq: (pid 1246) 120s
run: puma: (pid 1248) 120s

If any service is down, restart it individually:

sudo gitlab-ctl restart puma
sudo gitlab-ctl restart sidekiq

Health Check and Database Verification

# Run a full health check
sudo gitlab-rake gitlab:check

# Test email sending (run in Rails console)
sudo gitlab-rails console
# In the console, type:
Notify.test_email('[email protected]', 'GitLab Test', 'Test email').deliver_now

Monitoring with Prometheus and Grafana

GitLab has Prometheus and Grafana built in. Enable Grafana in gitlab.rb:

grafana['enable'] = true
grafana['http_port'] = 3000
sudo gitlab-ctl reconfigure
# Access: http://your-gitlab-url/-/grafana

The Grafana dashboard shows CPU/RAM usage per service (Puma, Sidekiq, Gitaly, PostgreSQL), along with request rate and latency. I keep this tab open during large deployments to spot bottlenecks immediately — it saves a significant amount of debugging time.

Automated Backups

# Create a manual backup
sudo gitlab-backup create

# View created backups
ls -lh /var/opt/gitlab/backups/

Add to root’s crontab to run automated backups at 2 AM:

sudo crontab -e
# Add the following line:
0 2 * * * /opt/gitlab/bin/gitlab-backup create CRON=1

Common Issues After 6 Months in Production

  • 502 Bad Gateway: Usually Puma hasn’t finished starting. Wait 1–2 minutes or run sudo gitlab-ctl restart puma.
  • SELinux blocking git operations: Run sudo ausearch -m avc | grep git and create a policy as described above.
  • Disk fills up fast: GitLab CI artifacts and the container registry eat disk space quickly. Set artifacts_expire_in in each project’s CI settings and clean up the registry periodically with sudo gitlab-ctl registry-garbage-collect.
  • Abnormally high RAM: Sidekiq sometimes develops a memory leak after running for many days — add sidekiq['enable_sidekiq_cluster'] = false or schedule periodic restarts via cron.

Add a simple alert when disk is nearly full:

# Add to crontab
*/30 * * * * df -h /var/opt/gitlab | awk 'NR==2 {gsub(/%/,"",$5); if ($5+0 > 80) print "GitLab disk usage: " $5 "%"}' | grep -q "%" && echo "Disk alert" | mail -s "GitLab Disk High" [email protected]

After 6 months in production, GitLab CE on Fedora has been more stable than I initially expected. SELinux was the most frustrating part in the first week — constantly discovering new operations that needed policy updates. But once that phase was done, I didn’t have to touch it for months. The biggest payoff is still the internal CI/CD with GitLab Runner: build times are noticeably shorter, no worrying about running out of minutes, and no dependency on upstream bandwidth.

Share: