For system administrators, manual deployments are often a source of stress and errors. Every step must be executed precisely, which costs time and introduces risks. GitLab CI/CD is the tool that transforms this process. It allows you, through a well-configured pipeline, to automatically test, build, and deploy with every code commit.
In this guide, we will show you how to set up a robust GitLab CI/CD pipeline for a real-world Laravel project. We will focus on best practices that prioritize security and efficiency.
Prerequisites
Before we begin, ensure you have the following components ready:
- GitLab CE Instance: A functioning GitLab Community Edition instance. If you don’t have one yet, we at admin-intelligence offer a hosted GitLab CE solution.
- GitLab Runner: At least one registered runner that uses either a Shell or Docker executor.
- Basic YAML Knowledge: You should be familiar with the syntax and indentation rules of YAML.
- SSH Access: Access to your deployment servers configured via SSH keys.
- Project Repository: A Git repository containing your application source code.
The Anatomy of a GitLab CI/CD Pipeline
A GitLab CI/CD pipeline is defined by Stages and Jobs. A typical and proven structure follows the pattern: Test → Build → Deploy. The stages are executed sequentially, while jobs within a stage can run in parallel by default.

This entire logic is defined in a single file in the root directory of your project: .gitlab-ci.yml.
stages:
- test
- build
- deploy
These three stages form the backbone of a modern CI/CD pipeline and ensure a clear separation of responsibilities: quality assurance, asset creation, and the actual deployment.
Stage 1: Test – Automated Quality Assurance
The test stage is your automated gatekeeper. Here, the code is checked for syntax errors, missing dependencies, and basic functionality before it is processed further.
test_php:
stage: test
script:
# Environment Check
- php --version || (echo "PHP not found on runner" && exit 1)
- composer --version || (echo "Composer not found on runner" && exit 1)
# Install and validate dependencies
- composer validate
- composer install --optimize-autoloader --no-interaction
# Laravel-specific checks
- cp .env.example .env
- php artisan key:generate --ansi
- php artisan config:cache --ansi
cache:
key: "$CI_COMMIT_REF_SLUG-vendor"
paths:
- vendor/
Key Details:
- Error Handling: The || exit 1 command ensures that the pipeline stops immediately in case of a critical error.
- Caching: The vendor/ directory is cached to drastically reduce installation time on subsequent pipeline runs.
Stage 2: Build – Creating Assets and Artifacts
In the build stage, frontend assets are compiled and artifacts are generated. These artifacts are the result of the build process and are passed on to the next stage.
build_frontend:
stage: build
script:
# Node.js Environment Check
- node --version || (echo "Node.js not found on runner" && exit 1)
- npm --version || (echo "npm not found on runner" && exit 1)
# Clean install for consistent builds and execute build
- npm ci --silent
- npm run build
cache:
key: "$CI_COMMIT_REF_SLUG-node"
paths:
- node_modules/
artifacts:
paths:
- public/build/
expire_in: 1 hour
The difference between cache and artifacts:
- Cache: Serves to optimize performance between different pipeline runs (e.g., node_modules/, vendor/).
- Artifacts: Are used to pass files between stages within the same pipeline run (e.g., the compiled CSS and JS files). zwischen Stages innerhalb desselben Pipeline-Laufs (z. B. die kompilierten CSS- und JS-Dateien).
Stage 3: Deploy – The Automated Deployment
The deploy stage is the heart of automation. Here, the tested and built code is transferred to the server.
deploy_production:
stage: deploy
needs:
- test_php
- build_frontend
only:
- main
before_script:
# Prepare SSH environment
- mkdir -p ~/.ssh
- echo "$DEPLOY_KEY" > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- echo -e "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile=/dev/null\n" > ~/.ssh/config
script:
# Activate Maintenance Mode
- ssh website@"$SERVER_IP" "cd $TARGET_FOLDER && php artisan down || true"
# Update code
- ssh website@"$SERVER_IP" "cd $TARGET_FOLDER && git pull"
# Upload pre-built assets
- scp -r public/build/ website@"$SERVER_IP":"$TARGET_FOLDER/public/"
# Run dependencies and database migrations only when needed
- ssh website@"$SERVER_IP" "cd $TARGET_FOLDER && composer install --no-dev --optimize-autoloader"
- ssh website@"$SERVER_IP" "cd $TARGET_FOLDER && php artisan migrate --force"
# Bring site back online
- ssh website@"$SERVER_IP" "cd $TARGET_FOLDER && php artisan up"
after_script:
# Fail-Safe: Ensures the site comes back online even if the script fails
- ssh website@"$SERVER_IP" "cd $TARGET_FOLDER && php artisan up || true"
Security: CI/CD Variables and DevSecOps
Sensitive data such as SSH keys or API keys never belong directly in the .gitlab-ci.yml. GitLab offers a secure solution for this: CI/CD Variables.

Best Practices for using Variables:
- Masked: Prevents the value of the variable from being displayed in the job logs.
- Protected: Ensures the variable is only available in pipelines for protected branches (e.g., main).
- File: Ideal for multi-line content like an SSH Private Key (DEPLOY_KEY).
- Environment Scope: Allows defining different values for different environments (e.g., Staging vs. Production).
Furthermore, it is crucial to establish security as a fixed component of your pipeline (DevSecOps). Extend your test stage with automated security scans:
- SAST (Static Application Security Testing): Analyzes your source code for known vulnerabilities.
- Dependency Scanning: Checks your composer.json or package.json for dependencies with known security vulnerabilities.
- Secret Detection: Prevents sensitive data from being accidentally committed to the repository.
These tools are often directly integrated into GitLab and can be added to your .gitlab-ci.yml with just a few lines.
Pipeline Efficiency through Advanced Configuration
- Parallel Jobs with needs: The needs keyword allows you to establish a dependency on jobs from previous stages. This means a job doesn’t have to wait for the entire previous stage to complete, significantly speeding up your pipeline.
- Staging vs. Production: Use extends to avoid code duplication and define different jobs for your environments.
- Error Handling with retry: Instruct GitLab to automatically retry a job on certain failures.
- Avoiding Race Conditions with resource_group: Prevent multiple pipelines from deploying to the same resource (e.g., your production server) simultaneously.
Conclusion: Your Pipeline is Just the Beginning
Congratulations! You have designed a robust, automated, and secure CI/CD pipeline for a Laravel project. This is more than just a technical achievement—it’s a fundamental step towards a modern DevOps culture. You have laid the groundwork to make releases faster, safer, and more reliable.
But your journey into automation doesn’t have to end here. The pipeline shown is a powerful foundation to build upon. As your projects grow and requirements increase, you will face new and exciting challenges.
Next Steps: From a Solid Base to DevOps Excellence
Consider the following points as your roadmap for the further development of your CI/CD processes:
- Integrated Security (DevSecOps): Start integrating automated security scans (SAST, Dependency Scanning) directly into your pipeline. This transforms security from an afterthought into an integral part of your development process.
- Advanced Deployment Strategies: If the availability of your application is critical, you should look into techniques like Blue-Green or Canary deployments. These enable zero-downtime deployments and significantly minimize risk during new releases.
- Comprehensive Monitoring and Observability: A pipeline that runs in the background is only half the story. Integrate notifications for Slack or Teams and use GitLab’s monitoring features to proactively monitor bottlenecks and error rates.
These advanced topics can be complex, but the benefit to the stability and efficiency of your infrastructure is immense. A stable foundation is essential. That’s why we offer professionally managed GitLab CE instances, so you can fully concentrate on optimizing your pipelines without worrying about platform maintenance.
Whether you have a specific question about your pipeline, want to discuss a complex challenge, or need support implementing these next steps – we look forward to the exchange. Contact us to continue the conversation.