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:latest Self-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.







