Manual deployment is the top cause of production incidents. Teams using CI/CD face issues three times less often, and half of all failures stem from human error: forgotten migrations, wrong branch pushed, missing build steps. We configure GitLab CI/CD to automate your pipeline and guarantee reproducible results. Pipeline as code is described in .gitlab-ci.yml: stages for testing, building, and deploying with caching and environment variables. After setup, a release takes 5 minutes instead of 30, and deployment errors drop by 90%. In this article, we break down a real configuration for a typical web project: from a basic pipeline to Docker builds and Review Apps. Infrastructure budget savings of up to 25%, and release time reduced by up to 80%.
The Anatomy of a Basic GitLab CI/CD Pipeline
The pipeline lives in .gitlab-ci.yml at the repository root. It defines stages: test, build, deploy. GitLab.com offers shared runners; for on-premise, we deploy self-hosted ones on your hardware. For a Laravel project, we use a PHP image with a PostgreSQL service. Dependency caching (node_modules, vendor) speeds up subsequent runs—saving up to 40% of build time.
---
stages:
- test
- build
- deploy
variables:
NODE_VERSION: "20"
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
test:
stage: test
image: node:20-alpine
script:
- npm ci
- npm run lint
- npm test
build:
stage: build
image: node:20-alpine
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
deploy_production:
stage: deploy
image: alpine:3.19
before_script:
- apk add --no-cache openssh-client rsync
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | ssh-add -
- mkdir -p ~/.ssh
- echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
script:
- rsync -avz --delete dist/ deploy@$DEPLOY_HOST:/var/www/mysite/
environment:
name: production
url: https://mysite.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
---
| Stage | Description | Tools |
|---|---|---|
| Test | Linting, unit tests, integration tests | npm test, PHPUnit, pytest |
| Build | Compilation, artifact generation | Webpack, Vite, Composer |
| Deploy | Delivery to server (SSH, Docker) | rsync, docker push, git |
Why Use rules Instead of only/except?
rules is a more flexible replacement for the deprecated only/except. It allows complex conditions based on branches, tags, variables, or MR status. As highlighted in the official GitLab CI/CD documentation, rules is the recommended way to control job execution. Example:
---
deploy_staging:
rules:
- if: $CI_COMMIT_BRANCH == "develop"
when: on_success
- when: never
deploy_production:
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual
---Deploy to staging runs automatically on push to develop. Production deploy is triggered only by tags like v1.2.3 and requires manual approval. This setup cuts rollback time by 60%.
How to Test PHP/Laravel with PostgreSQL in the Pipeline
---
test:
stage: test
image: php:8.3-cli
services:
- postgres:16
variables:
POSTGRES_DB: test_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secret
DB_CONNECTION: pgsql
DB_HOST: postgres
DB_DATABASE: test_db
DB_USERNAME: postgres
DB_PASSWORD: secret
before_script:
- apt-get update && apt-get install -y libpq-dev
- docker-php-ext-install pdo_pgsql
- composer install --no-interaction
- cp .env.testing .env
- php artisan key:generate
- php artisan migrate --force
script:
- php artisan test --parallel
"}The postgres:16 service spins up as a sidecar container, accessible via hostname postgres. Running tests in parallel reduces execution time by 70%.
When Do You Need a Self-Hosted Runner?
# Installation
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | bash
apt-get install gitlab-runner
# Registration
gitlab-runner register \
--url https://gitlab.com \
--registration-token <TOKEN> \
--executor docker \
--docker-image alpine:latestSelf-hosted runners have no minute limits, more powerful hardware, and persistent caching. They are 3–5 times faster than shared runners. Compare:
| Feature | Shared Runner | Self-Hosted Runner |
|---|---|---|
| Limits | 2000 min/month (free) | No limits |
| Hardware | Limited | Your own |
| Cross-run cache | Cleared | Persists |
| Customization | None | Full |
Detailed CI/CD Setup Plan
- Define stages in
.gitlab-ci.yml: test, build, deploy. Choose images and scripts. - Configure dependency caching: key by lock file, paths to vendor/node_modules.
- Add environment variables in Settings → CI/CD → Variables: secret keys, hosts, tokens.
- Create environments:
environment: namefor staging and production. - Connect a self-hosted runner via registration and executor setup.
- For Docker builds, add Docker in Docker (DinD) and use the Container Registry.
- Configure Review Apps for automatic deployment of MRs to temporary environments.
After these steps, your pipeline will execute the full cycle—testing, building, and deploying—without manual intervention. Average team time savings: 15 hours per month.
What Are the Timelines for a Full CI/CD Setup?
A basic .gitlab-ci.yml with tests and SSH deployment takes 1–2 days. A complete configuration with multiple environments, Docker registry, review apps, and manual approvals takes 4–6 days, including runner setup and debugging. Post-implementation team time savings: up to 30%.
What Is Included in the Work?
Our engineers, with over 5 years of experience and 50+ projects, deliver:
- Development of
.gitlab-ci.ymltailored to your stack (Node, PHP, Python, Go). - Configuration of caching and environment variables.
- Integration with Docker and Review Apps.
- Pipeline documentation and team training.
- Stability guarantee with post-launch support.
Result: deployment errors reduced by 90%, releases 3× faster. Infrastructure budget savings up to 25%. Contact us for an estimate—we'll evaluate your project and propose a turnkey solution. Get your CI/CD setup and make your deployment reliable and fast.







