Field Notes: Automating Deployments with GitLab Runner on a Cloud VPS
Field Notes: Automating Deployments with GitLab Runner on a Cloud VPS
When a mid‑size SaaS team moved from manual scp pushes to a repeatable CI/CD workflow, the bottleneck shifted from code‑write to code‑release. The team needed a lightweight, self‑hosted runner that could pull from their private GitLab instance, build Docker images, and push them to a private registry without exposing internal credentials. Budget constraints ruled out managed CI services, and the existing shared hosting could not guarantee the required network isolation. The solution: spin up a single‑CPU Cloud VPS, install GitLab Runner, and wire it into the existing pipeline.
Choosing the Host: Why a Cloud VPS Fits the Bill
For a CI runner we need predictable CPU cycles, a stable public IP, and the ability to install custom binaries. A Cloud VPS with DevNix Solutions offers exactly that: 1 vCPU, 1 GB RAM, and SSD storage that can be resized later if the build matrix grows. Because the VPS is isolated from the production web tier, any runaway build or mis‑configuration stays contained, reducing risk to live services.
Preparing the Environment
System Updates and Essential Packages
Start with a clean Debian‑based image. Apply security patches and install the tools the runner will need:
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl gnupg2 ca-certificates lsb-release
Creating a Dedicated System User
Running the runner under its own account limits the blast radius of a compromised build:
sudo adduser --system --group --home /home/gitlab-runner gitlab-runner
sudo usermod -aG docker gitlab-runner # if Docker builds are required
Installing GitLab Runner
The official binary is the most reliable source. Download, verify, and install it as a system service:
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
sudo apt-get install gitlab-runner
sudo systemctl enable --now gitlab-runner
Registering the Runner: Decisions, Risks, and Checks
Choosing the Executor
Two common executors are shell and docker. The docker executor isolates each job in its own container, which mitigates dependency drift but adds a layer of complexity. For this scenario we opted for Docker because the builds already produce container images.
Secure Registration Token Handling
Obtain the registration token from the GitLab project’s Settings → CI/CD → Runners page. Never store the token in a script; instead, feed it interactively or via an environment variable that is cleared after registration:
sudo gitlab-runner register \
--non-interactive \
--url https://gitlab.example.com/ \
--registration-token $CI_REG_TOKEN \
--executor docker \
--docker-image "docker:latest" \
--description "vps-runner-01" \
--tag-list "vps,docker" \
--run-untagged="true" \
--locked="false"
Risk Mitigation: Network Exposure
- Ingress filtering: Restrict inbound traffic to port 22 (SSH) and the GitLab Runner port (default 8093) using the VPS provider’s firewall.
- Credential leakage: Use GitLab’s masked variables for Docker registry passwords; the runner never writes them to disk.
- Resource exhaustion: Set
concurrent = 1in/etc/gitlab-runner/config.tomlto prevent multiple builds from overloading the 1 vCPU.
Integrating the Runner into the CI Pipeline
Update .gitlab-ci.yml to target the new runner tags and to push the built image to a private registry:
stages:
- build
- test
- deploy
build_image:
stage: build
tags:
- vps
- docker
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
test_image:
stage: test
tags:
- vps
- docker
script:
- docker run --rm $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA ./run-tests.sh
deploy_prod:
stage: deploy
tags:
- vps
- docker
when: manual
script:
- ssh deploy@prod.example.com "docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA && docker service update --image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA my_service"
Operational Checks: Keeping the Runner Healthy
Log Monitoring
GitLab Runner writes to /var/log/gitlab-runner/. Set up a simple logwatch or use journalctl -u gitlab-runner to catch failures early. Look for repeated “connection reset” messages, which often indicate network throttling on the VPS.
Resource Utilization
Because the VPS is modest, monitor CPU and memory with top or install netdata for real‑time dashboards. If average CPU usage exceeds 70 % during peak builds, consider scaling to the larger 2 vCPU tier.
Runner Version Drift
GitLab releases runner updates frequently. Schedule a monthly check:
sudo apt-get update && sudo apt-get install --only-upgrade gitlab-runner
Risk Review: What Could Go Wrong?
- Build timeouts: The default 1‑hour timeout may be insufficient for large Docker builds. Adjust
timeout = 7200in the runner config if needed. - Docker daemon compromise: If a malicious job gains root inside a Docker container, it could escape to the host. Mitigate by running the Docker daemon with
--userns-remapand limiting container capabilities. - Network latency to the GitLab server: A VPS in a distant region can add seconds to each clone. Choose a data center close to your GitLab instance to keep latency low.
Conclusion
By provisioning a modest Cloud VPS and installing a Docker‑based GitLab Runner, the team transformed a manual, error‑prone release process into a repeatable, auditable pipeline. The VPS provides enough isolation to protect production while keeping costs predictable. Ongoing operational checks—log review, resource monitoring, and runner version updates—ensure the system stays reliable as the codebase and team grow.