Self-hosted Gitlab CI for PHP Symfony project

Tommy
3 min readMar 29, 2024

--

Every few years in tech the bar of necessary minimum is raised. And that’s a good thing! But to keep up every project ought to be “in shape” and have a lot of tools set up: linters, tests, CI/CD pipeline, you name it…

In hope it could be of use to someone I’d like to share my humble efforts of configuring GitLab CI for php project.

Environment

I used self-hosted runner installed standalone on Digital Ocean droplet.

The runner

Had no luck adding official gitlab repo (https://docs.gitlab.com/runner/install/linux-repository.html) to ubuntu/kinetic 22.10 lts so I went with downloading binaries (https://docs.gitlab.com/runner/install/linux-manually.html)

curl -LJO "https://gitlab-runner-downloads.s3.amazonaws.com/latest/deb/gitlab-runner_amd64.deb"
dpkg -i gitlab-runner_amd64.deb

Register runner.

sudo gitlab-runner register

# you will be asked to provide url - https://gitlab.com
# registration token (CI/CD >> Settings >> Register runner)
# executor - docker
# name - whatever-you-like
# image - docker:stable
#/etc/gitlab-runner/config.toml
concurrent = 4
check_interval = 0
shutdown_timeout = 0

[session_server]
session_timeout = 1800

[[runners]]
name = "dedicated-runner"
url = "https://gitlab.com"
id = 24407945
token = "glrt-your-gitlab-token"
token_obtained_at = 2023-06-16T21:57:50Z
token_expires_at = 0001-01-01T00:00:00Z
executor = "docker"
environment = ["GIT_CONFIG_COUNT=1", "GIT_CONFIG_KEY_0=safe.directory", "GIT_CONFIG_VALUE_0=*"]
[runners.cache]
MaxUploadedArchiveSize = 0
[runners.docker]
tls_verify = false
image = "docker:stable"
privileged = true
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/cache", "/home/gitlab-runner/shared:/builds/shared"]
shm_size = 0
[runners.custom_build_dir]
enabled = true
default:
image: docker:24.0.2-git
tags:
- dedicated-runner-2

artifacts:
paths:
- vendor/
- var/cache/
expire_in: 10 min

services:
- name: docker:24.0.2-dind
alias: dockerdaemon

variables:
# Tell docker CLI how to talk to Docker daemon.
DOCKER_HOST: tcp://dockerdaemon:2375/
# Use the overlayfs driver for improved performance.
DOCKER_DRIVER: overlay2
# Disable TLS since we're running inside local network.
DOCKER_TLS_CERTDIR: ""
GIT_CLONE_PATH: $CI_BUILDS_DIR/shared/$CI_COMMIT_SHORT_SHA/$CI_JOB_ID

stages:
- build
- lint
- test

build:
stage: build
before_script:
- docker login -u swiftcode -p "$DOCKER_HUB_TOKEN"
script:
- docker compose build
- docker compose run --rm php sh -c "composer install && pwd && ls -la"

fixer:
stage: lint
needs:
- build
script:
- docker login -u swiftcode -p "$DOCKER_HUB_TOKEN"
- docker compose run --rm php ./vendor/bin/php-cs-fixer fix -v --dry-run --using-cache=no

psalm:
needs:
- build
stage: lint
script:
- ls -la
- pwd
- touch psalm.txt
- docker login -u swiftcode -p "$DOCKER_HUB_TOKEN"
- docker compose run --rm php ./vendor/bin/psalm --no-cache --no-progress --threads=1

tests:
needs:
- build
stage: test
before_script:
- docker login -u swiftcode -p "$DOCKER_HUB_TOKEN"
- docker-compose up -d --remove-orphans
script:
- docker-compose exec php sh -c "./vendor/bin/phpunit"
after_script:
- docker-compose down --rmi local --volumes

Downsides:

Docker in docker is good for isolation, too good… so every job is run inside own docker namespace and is a fresh instance, so caching is a challenge. But that is another story…

--

--