Self-Host GitLab CE on Proxmox
Deploy a private GitLab server on your own hardware using Proxmox and Ubuntu.
Introduction
30 Second Summary
Every line of code you push to GitHub or GitLab.com lives on someone else's server, governed by someone else's rules and pricing tiers.
In this project, you will deploy your own GitLab CE server on a Proxmox VM, giving you a fully private Git platform running on your own hardware at zero cost.
What You'll Build
A self-hosted GitLab Community Edition server running on an Ubuntu 24.04 VM inside Proxmox, accessible from any device on your local network for managing repositories and pushing code over SSH.
By the end of this project, you'll have:
- A Proxmox VM configured with 8 GB RAM, 4 vCPUs, and 40 GB disk using cloud-init for automated provisioning.
- GitLab CE installed via the Omnibus Linux package with PostgreSQL, Redis, and Nginx bundled in.
- A working Git workflow with SSH key authentication for secure, passwordless push and pull.
- Secret Mission: expose your GitLab to the internet securely using a Cloudflare Tunnel with your own domain and HTTPS.
Are there any prerequisites?
You need a Proxmox VE host with at least 8 GB of RAM available for the VM (a 16 GB mini PC works great). You should also be comfortable with basic Linux commands and SSH. For the Secret Mission, you will need a Cloudflare account with a domain.
Create the Ubuntu VM in Proxmox
Before you can install GitLab, you need a server to run it on. In this step, you'll create an Ubuntu 24.04 virtual machine inside Proxmox using a cloud image and cloud-init for automated configuration. This approach boots in seconds and auto-configures SSH keys and networking, skipping the manual Ubuntu installer entirely.
In This Step, Get Ready To:
- Download the Ubuntu 24.04 cloud image and create a VM with 8 GB RAM, 4 vCPUs, and 40 GB disk.
- Configure cloud-init with your SSH key and network settings, then boot the VM.
- Verify the VM is running and update all packages.
Download the Cloud Image and Create the VM
Ubuntu cloud images are pre-built, minimal server images designed for automated provisioning. Instead of clicking through an installer, you download a single .img file and import it directly as a VM disk.
- SSH into your Proxmox host.
ssh root@your-proxmox-ip
- Download the Ubuntu 24.04 Noble Numbat cloud image.
wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
Now you'll create the VM shell and import the cloud image as its disk. Choose a VM ID that is not already in use on your Proxmox host.
- Create a new VM with 8192 MB RAM, 4 CPU cores, a VirtIO SCSI controller, and a VirtIO network adapter.
qm create [[VMID="your VM ID, e.g. 100"]] --name gitlab --memory 8192 --cores 4 --scsihw virtio-scsi-pci --net0 virtio,bridge=vmbr0
What does this command do?
This creates an empty VM definition in Proxmox. The --scsihw virtio-scsi-pci flag is required for Ubuntu cloud images to detect the disk correctly. The --net0 virtio,bridge=vmbr0 connects the VM to your local network through the default Proxmox bridge.
- Import the downloaded cloud image as the VM's SCSI disk.
qm importdisk [[VMID="your VM ID, e.g. 100"]] noble-server-cloudimg-amd64.img local-lvm
- Attach the imported disk to the VM's SCSI controller.
qm set [[VMID="your VM ID, e.g. 100"]] --scsi0 local-lvm:vm-[[VMID="your VM ID, e.g. 100"]]-disk-0
- Add a cloud-init CD-ROM drive so the VM can read its configuration at boot.
qm set [[VMID="your VM ID, e.g. 100"]] --ide2 local-lvm:cloudinit
- Set the boot order to the SCSI disk.
qm set [[VMID="your VM ID, e.g. 100"]] --boot order=scsi0
- Resize the disk to 40 GB. GitLab needs about 2.5 GB just for the package install, and the rest covers PostgreSQL data, logs, and repository storage.
qm disk resize [[VMID="your VM ID, e.g. 100"]] scsi0 40G
Configure Cloud-Init and Start the VM
Cloud-init reads configuration from the CD-ROM drive you just added and applies it on first boot. This is where you set your SSH key, username, and network settings so the VM is ready to use the moment it starts.
- Set your SSH public key for authentication. This lets you SSH into the VM without a password.
qm set [[VMID="your VM ID, e.g. 100"]] --sshkey ~/.ssh/id_rsa.pub
- Configure the VM's network. Use DHCP if your router assigns IPs automatically, or set a static IP if you prefer a fixed address.
DHCP (Automatic IP)
qm set [[VMID="your VM ID, e.g. 100"]] --ipconfig0 ip=dhcp
Your router will assign an IP address to the VM automatically. You'll find this IP in the Proxmox console after boot.
Static IP
qm set [[VMID="your VM ID, e.g. 100"]] --ipconfig0 ip=[[STATICIP="your static IP, e.g. 192.168.1.100"]]/24,gw=[[GATEWAY="your gateway IP, e.g. 192.168.1.1"]]
Replace the IP and gateway with values that match your local network. Make sure the IP is outside your router's DHCP range to avoid conflicts.
- Set the default username for the VM.
qm set [[VMID="your VM ID, e.g. 100"]] --ciuser [[CIUSER="your preferred username"]]
- Start the VM.
qm start [[VMID="your VM ID, e.g. 100"]]
Wait about 30 seconds for cloud-init to finish applying your configuration. Then SSH into the VM.
ssh [[CIUSER="your username"]]@[[VMIP="your VM's IP address"]]
Pro tip: finding your VM's IP
If you used DHCP, check the Proxmox web UI. Click on your VM, then go to the Summary tab. The IP address appears in the network section once cloud-init has finished. You can also check your router's DHCP lease table.
I'm connected via SSH
You should see a terminal prompt with your username and the hostname gitlab. You're inside your new Ubuntu VM.
I see an error
Connection refused or timed out?
Cloud-init may still be running. Wait another 30 seconds and try again. If it still fails, open the VM's console in the Proxmox web UI to check if it booted successfully. Look for any cloud-init errors in the console output.
If you see "Permission denied (publickey)", your SSH key may not have been applied correctly. Verify that ~/.ssh/id_rsa.pub exists on your Proxmox host. Help me troubleshoot my SSH connection to the VM.
Update Packages and Verify Disk Space
Cloud images ship with a minimal package set that may be slightly outdated. Updating everything now ensures GitLab installs on a clean, current system.
- Update and upgrade all packages.
sudo apt update && sudo apt upgrade -y
- Verify you have enough disk space. You should see approximately 40 GB available on the root partition.
df -h
What should I see?
Look for the line showing / as the mount point. The size column should show around 39-40 GB. The cloud image itself only uses a couple of GB, so most of the space will be available for GitLab.
Why cloud-init instead of a manual ISO install?
Cloud images with cloud-init are the standard Proxmox workflow for fast VM provisioning. A manual ISO install requires clicking through the Ubuntu installer, setting up users, and configuring networking by hand. Cloud-init does all of this automatically from the configuration you set with qm set. This makes your VM setup repeatable and scriptable.
Your Ubuntu VM is up, updated, and ready for action. Next up, you'll install GitLab CE and have a fully functional DevOps platform running on this server.
Install GitLab CE
Your Ubuntu VM is up and running. Now it is time to install the star of the show: GitLab CE. GitLab's Omnibus Linux package bundles everything the platform needs into a single install. That includes PostgreSQL, Redis, Nginx, and Sidekiq. One command, and your entire DevOps platform comes to life.
In This Step, Get Ready To:
- Install dependencies and add the official GitLab CE repository.
- Install GitLab CE with the Omnibus package and retrieve your initial root password.
- Verify GitLab is running by loading the web interface in your browser.
Install Dependencies and Add the GitLab Repository
Before installing GitLab itself, you need a couple of prerequisites. The curl tool fetches the GitLab repository script, and UFW (Uncomplicated Firewall) needs to allow traffic on the ports GitLab uses.
- SSH into your GitLab VM if you are not already connected.
- Install curl if it is not already present.
sudo apt install -y curl
- Open the firewall ports that GitLab needs: SSH (22), HTTP (80), and HTTPS (443).
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
What do these ports do?
Port 22 is for SSH connections, which you are using right now to manage the VM. Port 80 handles HTTP web traffic, which is how you will access GitLab's web interface. Port 443 is for HTTPS, which you will need if you set up TLS later or tackle the secret mission.
UFW may prompt you to confirm enabling the firewall. Type y and press Enter. Since you already allowed port 22, your SSH session will stay connected.
- Add the official GitLab CE package repository to your system.
curl --location "https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh" | sudo bash
What does this command do?
This downloads a shell script from GitLab's official package server and runs it with root privileges. The script adds GitLab's APT repository to your system so that apt install knows where to find the gitlab-ce package.
Install GitLab CE With Your External URL
GitLab needs to know the URL that users will type into their browser to reach it. You pass this as an environment variable during installation. Since you are on a local network without a domain name, you will use your VM's IP address with http://.
- Run the following command, replacing the IP address with your VM's actual IP.
sudo EXTERNAL_URL="http://[[VMIP="YOUR VM's IP ADDRESS e.g. 192.168.1.100"]]" apt install gitlab-ce
What does this command do?
The EXTERNAL_URL environment variable tells GitLab how it should be accessed. GitLab uses this to configure Nginx, generate links in the web UI, and set up Git remote URLs. The apt install gitlab-ce command then downloads and installs the Omnibus package, which automatically configures PostgreSQL, Redis, Nginx, Sidekiq, and all other GitLab components.
Pro tip: grab a coffee
This installation takes several minutes. GitLab is setting up an entire DevOps platform from scratch. You will see a lot of output scroll by as it configures each component.
Why Omnibus instead of Docker?
GitLab officially recommends the Omnibus Linux package as the most mature and scalable installation method. It is the same approach used on GitLab.com itself. Running GitLab in Docker adds complexity around volume management and container networking without teaching you GitLab administration skills. The Omnibus package gives you direct access to GitLab's configuration files and services.
- Once the installation completes, retrieve the initial root password.
sudo cat /etc/gitlab/initial_root_password
Save this password now
Copy the password and store it somewhere safe. The file at /etc/gitlab/initial_root_password is automatically deleted after 24 hours for security. You will need this password to log in for the first time in the next step.
Verify GitLab Is Running
The installation is done, but you need to confirm that all of GitLab's services started correctly and that the web interface is reachable.
- Open a browser on any device on your local network.
- Navigate to http:// followed by your VM's IP address (e.g., http://192.168.1.100).
You should see the GitLab login page with fields for username and password.
I see the GitLab login page
GitLab is up and running. You are ready to move on to the next step where you will log in and create your first project.
The page does not load
GitLab has many services that all need to be running. Let's check their status.
- Back in your SSH session, check the status of all GitLab services.
sudo gitlab-ctl status
Every service should show run with a process ID and uptime. If any services show as down, re-apply the configuration.
- Run the reconfigure command to re-apply GitLab's configuration.
sudo gitlab-ctl reconfigure
Wait for the reconfigure to finish, then try loading the page again in your browser.
Still not loading?
Make sure your browser device is on the same network as the Proxmox host. Also verify that UFW is allowing port 80 by running sudo ufw status. If you are still stuck, help me troubleshoot my GitLab installation.
Your GitLab server is installed and serving its web interface on your local network. Next up, you will log in as the root admin, set up SSH keys, and push your first code to your very own Git server.
Configure GitLab and Create Your First Project
Your GitLab CE instance is installed and running. Now it is time to turn it into a working DevOps platform. In this step, you will secure the admin account, add your SSH key for passwordless Git operations, and push your first commit to a project hosted entirely on your own hardware.
In This Step, Get Ready To:
- Secure the root account by changing the default password.
- Add your SSH public key to GitLab for authenticated Git operations.
- Create your first project and push code from your local machine.
Log In and Secure the Root Account
GitLab generates a random password for the root admin account during installation. This password is stored in a temporary file that GitLab automatically deletes after 24 hours. You need to log in and set a permanent password before that happens.
- Open a browser on any device on your network and navigate to http:// followed by your VM's IP address.
- Sign in with username root and the password you retrieved from /etc/gitlab/initial_root_password in the previous step.
- Click the avatar in the top-left corner, then select Edit Profile.
- Set an email address for the root account and click Update profile settings.
- Navigate to User Settings, then Password.
- Change the root password to something secure that you will remember.
Why change the password now?
The file at /etc/gitlab/initial_root_password is automatically deleted after 24 hours for security. If you do not change the password before then, you will be locked out of the root account and need to reset it from the command line.
Pro tip: create a personal user
It is good practice to create a separate personal user account instead of using root for daily work. Go to the Admin Area (wrench icon in the left sidebar), then Users, then New User to create one.
Add Your SSH Key to GitLab
GitLab uses SSH key authentication for Git push and pull operations. The SSH key you added via cloud-init lets you access the VM itself. Adding your key to GitLab's web interface is a separate step that lets you push code to Git repositories without typing a password every time.
- On your local machine (not the VM), copy your SSH public key.
cat ~/.ssh/id_rsa.pub
What does this command do?
This prints your public SSH key to the terminal so you can copy it. The public key is safe to share. It is the private key (id_rsa without .pub) that must never be shared.
- Select and copy the entire output, starting with ssh-rsa.
- In GitLab, click your avatar in the top-left corner and select Preferences.
- Select SSH Keys in the left sidebar.
- Paste your public key into the Key field.
- Click Add key.
Why do I need two SSH keys?
It is the same key, but registered in two different places. The cloud-init SSH key lets you SSH into the Ubuntu VM to run commands. The GitLab SSH key lets you push and pull Git repositories. They serve different purposes even though they use the same key file.
Create Your First Project and Push Code
With your account secured and SSH key in place, you can now create a Git repository on your self-hosted GitLab and push code to it from your local machine. This proves the entire workflow is functional.
- In GitLab, click New Project on the dashboard.
- Select Create blank project.
- Enter a project name that is relevant to your work, such as devops-scripts or ai-agent-configs.
- Set the visibility level to Private.
- Uncheck Initialize repository with a README since you will push your own files.
- Click Create project.
GitLab shows an empty project page with instructions for pushing an existing repository. Now switch to your local machine's terminal to create a repository and push it.
- On your local machine, create a new directory and initialize a Git repository.
mkdir my-project && cd my-project
git init
echo "# My First GitLab Project" > README.md
git add .
git commit -m "Initial commit"
What does this command do?
This creates a new folder called my-project, initializes it as a Git repository, creates a simple README.md file, stages all files, and makes your first commit.
- Add your GitLab instance as the remote origin and push.
git remote add origin git@[[VMIP="YOUR VM IP ADDRESS"]]:root/my-project.git
git push -u origin main
What does this command do?
The git remote add origin command tells Git where to push your code. The URL uses the SSH protocol (git@) so Git authenticates using the SSH key you added to GitLab. The git push -u origin main command uploads your commit and sets main as the default upstream branch.
If this is your first time connecting to the VM over SSH for Git, you will see a fingerprint confirmation prompt. Type yes to continue.
- Go back to your GitLab project page in the browser and refresh the page.
You should see your README.md file and your initial commit displayed in the project. Your self-hosted Git workflow is fully operational.
I see my commit
Your code is now hosted on your own GitLab instance. Every push and pull goes directly to your hardware with zero third-party involvement.
I see an error
Having trouble pushing?
- If you see Permission denied (publickey), your SSH key was not added to GitLab correctly. Go back to Preferences and verify the key is listed under SSH Keys.
- If you see Connection refused, check that port 22 is open on the VM by running sudo ufw status on the VM.
- If the remote URL is wrong, fix it with git remote set-url origin git@<YOUR-VM-IP>:root/my-project.git.
Still stuck? Help me debug my Git push to self-hosted GitLab.
Your GitLab instance is configured and your first project is live. Next up, you will take on a secret mission to expose your GitLab to the internet securely using a Cloudflare Tunnel.
Secret mission
Expose GitLab with a Cloudflare Tunnel
Your GitLab works great on your local network. But what if you need to push code from a coffee shop, or collaborate with someone outside your home? In this secret mission, you will expose your GitLab instance to the internet securely using a Cloudflare Tunnel with your own domain. No port forwarding required.
Before you go
Clean Up Your Resources
This project runs entirely on your own hardware, so there are no ongoing cloud costs. Decide whether to keep your GitLab VM running, pause it to come back later, or delete it entirely.
Resources you used:
- Proxmox VM running Ubuntu 24.04 (8 GB RAM, 4 vCPUs, 40 GB disk).
- GitLab CE installed via the Omnibus Linux package inside the VM.
- SSH key added to your GitLab account.
- Local Git repository configured with a remote pointing to your GitLab instance.
- Cloudflare Tunnel and DNS record (secret mission only).
Keep everything running
No action needed. Choose this if you are still actively building on your GitLab instance or plan to use it as your daily Git server.
- Your GitLab VM will continue running and consuming 8 GB of RAM on your Proxmox host.
- GitLab services start automatically on boot, so your instance will be available whenever the VM is running.
- If you set up a Cloudflare Tunnel, it will also continue running as a systemd service.
Pause - I'll come back to this later
Shut down the VM to free up RAM and CPU on your Proxmox host. All your data, repositories, and configuration will be preserved on disk.
- SSH into your GitLab VM and run sudo shutdown now to gracefully shut down the VM.
- Alternatively, in the Proxmox web UI, select your GitLab VM and click Shutdown.
- To resume later, select the VM in Proxmox and click Start. GitLab services will start automatically.
- If you set up a Cloudflare Tunnel, the cloudflared service will also restart automatically when the VM boots.
Delete - I don't want to use this again
Remove all project resources and start fresh. This permanently deletes your VM, all repositories, and GitLab configuration.
- If you set up a Cloudflare Tunnel, go to the Cloudflare Zero Trust dashboard, navigate to Networks > Tunnels, and delete your tunnel. Remove the DNS CNAME record for your GitLab subdomain as well.
- In the Proxmox web UI, select your GitLab VM.
- Stop the VM if it is still running by clicking Stop.
- Click More > Remove and check the box to destroy unreferenced disks.
- On your local machine, remove the SSH key from your GitLab account if you no longer need it.
- Delete the local Git repository folder you created during the project.
- Remove the downloaded Ubuntu cloud image from your Proxmox host if you no longer need it.
Mission accomplished
That's a Wrap!
Nice work! You now have your own private GitLab server running on your own hardware. No cloud subscriptions, no per-user pricing, and full control over every line of code you push.
Here's what you learned:
- Provisioned an Ubuntu 24.04 VM in Proxmox using cloud-init for fast, repeatable server deployments.
- Installed and configured GitLab CE using the official Omnibus Linux package, which bundles PostgreSQL, Redis, Nginx, and Sidekiq into a single install.
- Set up SSH key authentication and pushed your first commit to a self-hosted Git repository.
- Exposed your GitLab instance to the internet securely using a Cloudflare Tunnel with HTTPS and optional Zero Trust access controls.
Ready to quiz yourself?