Self-Hosting Gitlab on Cycle
A walkthrough for deploying GitLab Community Edition on Cycle using the Cycle portal.
End state: a stateful GitLab container running in an isolated environment, addressable over a TLS-terminated hostname, and backing itself up on a schedule.
Prerequisites
You need:
- An account on a Cycle and access to a hub with at least one cluster and one server attached. If you're new to Cycle, run through Getting Started first.
- A DNS zone added to the hub. See Configuring a DNS Zone.
- A backup destination integration (Backblaze) connected at the hub level if you want automated backups. See Configure Backblaze.
- Capacity for GitLab CE's minimum recommended resources: 4 GB RAM, 4 cores. 8 GB+ is comfortable. (Keep in mind that CycleOS and services will take up a few hundred MB of RAM and a bit of CPU).
1. Sign in to the Hub
Open US portal or EU portal and sign in. Custom-cluster hubs use a different URL — check with your teams admin.
The left-hand nav is where the rest of this guide happens. The entries you'll use: Images, Environments, DNS, Integrations.
2. Create the image source
An image source tells Cycle where to fetch an image from. Each source can be imported repeatedly to produce point-in-time images, which makes pinning and rollback straightforward.
- Left nav: Images → Sources.
- Click Create in the top right.
- Fill in:
- Name:
GitLab CE - Source type: Docker Hub
- Target:
gitlab/gitlab-ce:latest, or a pinned tag likegitlab/gitlab-ce:17.5.0-ce.0for reproducibility - The image is public, so no credentials are needed. Click Create Image Source.
Creating the source doesn't pull anything yet. That happens in the next step.
Docs: Image sources · Creating an image source
3. Import the image
On the image source detail page, find the Import Image button at the top.
Click and hold the button until it triggers. A single click does nothing, this is intentional friction to prevent accidental re-pulls.
Cycle pulls the image, processes it, and the resulting image lands in live state. It's now selectable when you create a container.
Each import produces a new point-in-time copy. Old copies aren't replaced; they accumulate until you prune them.
Docs: Importing an image
4. Create the environment
An environment is Cycle's network boundary. Containers in the same environment share a private, encrypted network. Containers in different environments don't see each other by default.
- Left nav: Environments → Create.
- Fill in:
- Name:
gitlab - Cluster: the cluster that should host this environment
- Description: optional
- Leave Legacy Networking off unless you have a specific reason to enable IPv4 inside the private network.
- Click Create Environment.
Docs: Create an environment · Introduction to environments · Legacy Environments
5. Create the GitLab container (stateful)
From the environment dashboard, click Deploy Container in the top right.
In the deploy wizard:
- Container Name:
gitlab - Stateful: Choose Stateful from the dropdown, the container needs persistent volumes for config, data, and logs.
- Deployment Strategy can be left as default.
- Image: select the GitLab image you imported in step 3 from the Recent Images dropdown.
- Desired Instances: 1
Volumes
GitLab omnibus needs three persistent paths:
Mount path | Size for Demo | Suggested for Production | Purpose |
|---|---|---|---|
| 5 GB | 5 GB | configuration |
| 5 GB | 100 GB+ | repositories, database, attachments, CI artifacts |
| 5 GB | 10 GB | logs |
Network
Mark the Public Network setting to enabled for this demo as we will be reaching this container over public networking. Gitlab can be run behind the Cycle VPN service if preferred but that configuration is not covered here.
Click Deploy Container to finish the wizard. The container is created but not yet running.
Docs: Deploying a container · Container volumes
6. Start the environment
From the environment dashboard, click Start All in the top right. This boots Discovery and the GitLab container.
Because the Gitlab container has the public network setting of "Enabled", the load balancer will automatically start here. Give it 10-15 seconds to negotiate its IP from the provider.
Docs: Managing containers
7. Verify the container is running
Open the GitLab container from the containers dashboard (environment dashboard - click Containers from the horizontal nav), then go to the Instances tab.
The instance row shows health status. Click into the instance to open the Instance Console, live stdout/stderr from the running process. The first boot takes a few seconds to minutes depending on GitLab's omnibus initial reconfigure.
The container is healthy when:
- Instance state is
running - The console shows GitLab's reconfigure completing without errors
- Requesting the container's internal address returns the GitLab login page
If the instance is stuck in starting or restarting on a loop, check the Instance Console for the actual error. Common causes: under-sized volumes, malformed GITLAB_OMNIBUS_CONFIG, port conflicts.
Docs: Container instances
8. Add a LINKED DNS record
A LINKED record is a Cycle DNS record type that associates a domain with container(s). It resolves to the environment's Load Balancer and can terminate TLS automatically using Let's Encrypt or user uploaded certificates like Cloudflare's Origin certs.
- Left nav: DNS → Zones → select your zone.
- Click Add Record.
- In the wizard:
- Type: LINKED
- Name: the subdomain.
gitlabforgitlab.yourdomain.com, or@for the zone apex. - Environment: the environment from step 4
- Link to Container: the GitLab container
- TLS Enabled: ON
- Click Create.
DNS propagation takes a few minutes. TLS cert generation is rate-limited to 3 certs per domain per hour; if you exceed it, Cycle backs off for an hour before retrying.
Docs: Creating a LINKED record · DNS records
9. Configure automated backups
Cycle's backup model is command-based, not snapshot-based. The container declares a backup command that writes to stdout and a restore command that reads from stdin. Cycle runs them on a schedule and ships the result to your backup destination.
GitLab's native backup tooling streams cleanly into this model. The canonical commands are:
backup
gitlab-backup create BACKUP=streamed_backup SKIP=registry >/dev/stderr 2>&1 && tar -czf - /etc/gitlab /var/log/gitlab /var/opt/gitlab/backups/streamed_backup_gitlab_backup.tar && rm /var/opt/gitlab/backups/streamed_backup_gitlab_backup.tar restore
tar -xzf - -C && gitlab-ctl stop puma && gitlab-ctl stop sidekiq && gitlab-backup restore BACKUP=streamed_backup force=yes >/dev/stderr && gitlab-ctl reconfigure && gitlab-ctl restart Setup in the Hub:
- Open the GitLab container.
- Go to the Backups tab.
- Configure:
- Backup command: the streaming backup one-liner from the runbook
- Restore command: the streaming restore one-liner from the runbook
- Schedule: daily is typical. Pick an off-peak hour.
- Destination: the Backblaze integration configured at the hub level
- Save.
The first scheduled run produces a backup. Restores are also done through the Backups tab — Cycle pipes the archive into your restore command on stdin.
Restoring does not restart the container automatically. After a restore, GitLab may need gitlab-ctl reconfigure and a restart.
Docs: Container backups concept · Backup/restore commands · Configure Backblaze
Operating notes
A few things worth knowing once GitLab is running:
Resources. GitLab CE is heavy. 4 GB RAM is the floor; 8 GB or more is comfortable. Storage for /var/opt/gitlab grows with the number of repos and CI artifacts.
Upgrades. Re-import the image source to pull a newer Docker Hub tag. The import produces a new image; reimage the container if needed.
Git over SSH. GitLab's SSH listens on port 22 inside the container. If you want git-over-SSH externally, either remap it via gitlab_rails['gitlab_shell_ssh_port'] in GITLAB_OMNIBUS_CONFIG, or have the Load Balancer expose a different external port.
Backup destination housekeeping. Manage the Backblaze bucket from the Cycle Portal only. Editing or deleting backup files directly in Backblaze de-syncs Cycle's record of what exists.