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—enabling automated workflows triggered by code updates, including deployment to the backend server.
Installing and Registering the GitLab Runner
First, locate the Runner configuration in your GitLab project. Two values are required: url and token.
Next, install the GitLab Runner on the machine intended to execute jobs, following the official documentation.
Choose the appropriate installation method for your operating system. The process is straightforward—just follow the instructions step-by-step.
After installation, verify that the Runner 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 to associate it with your project. Use the following commands:
[root@localhost ~] sudo gitlab-runner register
Runtime platform arch=amd64 os=linux pid=109187 revision=9ffb4aa0 version=18.8.0
Running in system-mode.
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
Enter a description for the runner:
[localhost.localdomain]: Any descriptive name (for easy identification)
Enter tags for the runner (comma-separated):
Leave blank if unsure of their purpose
Enter optional maintenance note for the runner:
Leave blank if unsure of their purpose
WARNING: Support for registration tokens and runner parameters in the 'register' command has been deprecated in GitLab Runner 15.6 and will be replaced with support for authentication tokens. For more information, see https://docs.gitlab.com/ci/runners/new_creation_workflow/
Registering runner... succeeded correlation_id=xxxxx runner=xxxx
Enter an executor: custom, virtualbox, docker, kubernetes, docker-autoscaler, shell, ssh, parallels, docker-windows, docker+machine, instance:
docker
# Choose `docker` if uncertain
Enter the default Docker image (for example, ruby:3.3):
ruby:3.3
# Any Docker image works—it need not match your project's stack
Runner registered successfully. Feel free to start it, but if it's already running, 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 status indicator means it’s ready to run jobs.
Ensuring the Runner Can Use Docker
If your network environment can directly access Docker Hub, no further action is needed. Otherwise, configure a domestic mirror source.
Important Warning!
Running systemctl restart docker after changing the mirror source will terminate all running containers—including those belonging to other users!
Running systemctl restart docker after changing the mirror source will terminate all running containers—including those belonging to other users!
Running systemctl restart docker after changing the mirror source will terminate all running containers—including those belonging to other users!
To restart the Docker daemon without stopping containers, refer to:
For instructions on configuring Docker mirror sources, see:
A list of currently functional Docker mirror sources in China:
After configuring the mirror, pull the required Docker image on the Runner host to confirm availability. If pulling fails, try another mirror source.
[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
Next, edit the Runner configuration file (path displayed during registration; usually /etc/gitlab-runner/config.toml). By default, GitLab Runner pulls the specified Docker image from Docker Hub on every job. To avoid repeated downloads, set pull_policy = "if-not-present" so images are pulled only when missing locally.
After modification, the relevant section should resemble:
# Omitted preceding content
[[runners]]
name = "Arbitrary Name"
url = "Your GitLab 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 operational.
Writing the CI/CD Configuration File
This part is still under exploration—we’ll provide a concise 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 the automation steps executed on each trigger. In the example below, the alpine:latest Docker image is used, and a series 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"



