Background
We want to automatically deploy code updates to the backend server.
Our organization currently uses a self-hosted GitLab instance that supports GitLab Runners—similar to GitHub Actions workflows—so in theory, we can automate a series of operations after code updates, including deployment to the backend server.
Installing and Registering a GitLab Runner
First, locate the Runner configuration in your GitLab project. There are two required values: url and token.
Next, install GitLab Runner on the machine intended to run it, following the official documentation.
Select the appropriate installation method for your operating system. The process is straightforward—just follow the instructions in the documentation.
After installation, verify that the service is running:
[root@localhost ~]# sudo gitlab-runner status
Runtime platform arch=amd64 os=linux pid=83410 revision=9ffb4aa0 version=18.8.0
gitlab-runner: Service is running
Then register the Runner and associate it with your project. Follow the steps below:
# Register the Runner
[root@localhost ~] sudo gitlab-runner register
# Enter the two required configuration values: url and token
Enter the GitLab instance URL (for example, https://gitlab.com/):
The URL from your GitLab repository
Enter the registration token:
The token from your GitLab repository
# Other configuration options can be skipped by pressing Enter
Enter a description for the runner:
[localhost.localdomain]: Any descriptive name (to help distinguish this Runner)
Enter tags for the runner (comma-separated):
Enter optional maintenance note for the runner:
# Select Docker as the executor
Enter an executor: custom, virtualbox, docker, kubernetes, docker-autoscaler, shell, ssh, parallels, docker-windows, docker+machine, instance:
docker
# Choose any Docker image; it’s unrelated to your project
Enter the default Docker image (for example, ruby:3.3):
ruby:3.3
# Upon successful completion, you’ll see confirmation and the config file path
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
Configuration (with the authentication token) was saved in "/etc/gitlab-runner/config.toml"
After registration, you’ll see the Runner appear in your GitLab project’s CI/CD settings. A green indicator means it’s ready to run.
Ensuring the Runner Can Use Docker
If your network environment allows direct access to Docker Hub, you can proceed directly. Otherwise, configure a mirror (registry mirror).
Warning!
Running systemctl restart docker after changing the mirror will stop all running containers—including those belonging to other users!
Running systemctl restart docker after changing the mirror will stop all running containers—including those belonging to other users!
Running systemctl restart docker after changing the mirror will stop all running containers—including those belonging to other users!
To restart the Docker daemon without stopping existing containers, see:
To configure a Docker registry mirror, see:
Currently available Docker mirrors in China:
After configuring the mirror, pull the required image on the Runner host to confirm availability. If pulling fails, try another mirror.
[root@localhost ~]# docker pull ruby:3.3
3.3: Pulling from library/ruby
Digest: sha256:5e8b64d721461f5dbd6410167f358776a24832223d9885ffa83d1535ae1abe72
Status: Image is up to date for ruby:3.3
docker.io/library/ruby:3.3
Then edit the Runner configuration file—its location is shown after registration, typically at /etc/gitlab-runner/config.toml.
By default, GitLab Runner pulls the Docker image specified in the image keyword from Docker Hub on every job. Change pull_policy to "if-not-present" so the image is pulled only when not already present locally.
After modification, the relevant section may look like this:
# Omitted preceding content
[[runners]]
name = "arbitrary-name"
url = "your-url"
id = 7
token = "your-token"
token_obtained_at = 2026-01-28T09:33:31Z
token_expires_at = 0001-01-01T00:00:00Z
executor = "docker"
[runners.cache]
MaxUploadedArchiveSize = 0
[runners.cache.s3]
[runners.cache.gcs]
[runners.cache.azure]
[runners.docker]
tls_verify = false
image = "ruby:3.3"
privileged = false
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/cache"]
pull_policy = ["if-not-present"]
shm_size = 0
network_mtu = 0
At this point, your Runner should be fully operational.
Writing a CI/CD Configuration File
This part is still being explored—here’s a brief overview:
Navigate directly to your repository’s CI/CD page and use the built-in editor to create or modify .gitlab-ci.yml.
This file defines what automated actions should occur on each pipeline run. In the example below, the alpine:latest Docker image is used, and a set of commands runs whenever the dev branch is updated:
stages: # List of stages for jobs, and their order of execution
- deploy
deploy-to-dev: # Deploy code to dev server when `dev` branch is updated.
stage: deploy
image: alpine:latest
only:
- dev
before_script:
# Install required tools
- apk add --no-cache openssh-client rsync bash
# Prepare SSH key (set SSH_PRIVATE_KEY in CI/CD variables)
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
# Add known hosts: either set SSH_KNOWN_HOSTS in CI variables or fetch it automatically
- if [ -n "$SSH_KNOWN_HOSTS" ]; then echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts; else ssh-keyscan -H xxx >> ~/.ssh/known_hosts; fi
script:
# Push repo contents to remote via rsync. Exclude files listed in rsync-exclude-file.txt if present.
- echo "Starting rsync to $DEPLOY_SERVER:$DEPLOY_PATH"
- rsync -avz --progress --delete --exclude-from='rsync-exclude-file.txt' ./ "$DEPLOY_SERVER:$DEPLOY_PATH"
variables:
DEPLOY_SERVER: "root@x.x.x.x"
DEPLOY_PATH: "/data/my_path"
only:
- dev
when: on_success
environment:
name: dev
url: "ssh://$DEPLOY_SERVER"



