GitLab CI Symfony Infrastructure Integration

The integration of Symfony projects within a GitLab CI/CD ecosystem represents a sophisticated intersection of modern PHP framework architecture and automated DevOps orchestration. Implementing a continuous integration pipeline for Symfony requires a deep understanding of containerization, environment variable management, and the specific lifecycle of PHP applications. This process involves transitioning from a local development environment, often utilizing Docker or Podman, to a remote automated pipeline where build, test, and deployment phases are executed in isolation. The primary objective is to ensure that every commit undergoes rigorous validation—including functional testing and security audits—before reaching a production environment. This necessitates the configuration of specialized Docker images, the management of database migrations within ephemeral containers, and the orchestration of complex pipeline stages to maintain software quality and stability.

Development Environment and Containerization

The foundation of a Symfony project's lifecycle begins with the establishment of a consistent development environment. To eliminate the "it works on my machine" phenomenon, developers utilize containerization to ensure that the local environment mirrors the CI/CD runner.

For those initiating a new project, Docker serves as the primary engine for bootstrapping the Symfony skeleton without requiring a local PHP installation on the host system. This is achieved by executing a specific Docker run command that maps the local directory to the container's working directory.

docker run --user=1000 -v $(pwd):/var/www/html -w /var/www/html dockerhub.cwd.at/docker/php/cli:7.4.0 composer create-project symfony/website-skeleton demo-project

The use of the -v flag allows for volume mapping, ensuring that the files generated by Composer inside the container are persisted on the host machine. This allows developers to use their preferred IDE on the host while the actual execution occurs within the PHP CLI container.

In environments where Podman is utilized instead of Docker, the -v option remains functional due to the use of OverlayFS via Fuse. This ensures that volume management remains consistent across different container runtimes, allowing developers to transition between Docker and Podman with minimal configuration changes.

To provide a full-stack experience, a docker-compose.yml file is implemented to coordinate multiple services, such as the web server and the database. A critical component of this setup is the web server configuration, specifically the vhosts.conf file, which defines how the server handles requests.

apache <VirtualHost *:80> ServerName localhost DocumentRoot /var/www/public DirectoryIndex /index.php <Directory /var/www/public> AllowOverride None Order Allow,Deny Allow from All FallbackResource /index.php </Directory> <Directory /var/www/public/bundles> FallbackResource disabled </Directory> ErrorLog /var/log/apache2/project_error.log CustomLog /var/log/apache2/project_access.log combined </VirtualHost>

This configuration ensures that all requests are routed through the index.php entry point, which is fundamental to the Symfony front-controller pattern. Once the configuration is set, the stack is initialized using the command docker-compose up -d, allowing the application to be accessed via http://127.0.0.1:8080/.

Database Management and Entity Orchestration

A Symfony application's data layer requires precise synchronization between the application code and the database schema. This is managed through the Symfony CLI and Doctrine migrations.

The connection to the database is defined in the .env file. For a GitLab-integrated environment, the DATABASE_URL is configured as follows:

DATABASE_URL="mysql://root:@db_gitlab:3306/db_name?serverVersion=5.7"

The use of db_gitlab as the hostname indicates that the database is running in a separate container within the same Docker network, allowing for seamless communication. To prepare the database, developers must execute commands within the container's shell.

To enter the shell of the web container, the following command is used:

docker exec -it www_docker_gitlab bash

Once inside the container, the database is created using the Symfony console:

php bin/console doctrine:database:create

The development of the application's data model involves creating entities and managing migrations. For example, creating a Demo entity with a string field involves a three-step process:

  1. Generate the entity: php bin/console make:entity Demo
  2. Generate the migration file: php bin/console make:migration
  3. Execute the migration to update the schema: php bin/console doctrine:migrations:migrate

This workflow ensures that the database schema is version-controlled and can be reproduced exactly in the CI/CD pipeline.

GitLab Repository Configuration and Initial Commit

Once the local environment is stabilized, the project must be integrated into GitLab. Symfony initializes a Git repository by default, but the remote origin must be linked to the GitLab instance.

The process involves the following commands:

git init
git remote add origin [email protected]:ramet/gitlab-symfony.git
git add .

The establishment of this link enables the synchronization of local development with the remote repository, triggering the CI/CD pipeline upon every push.

GitLab CI Pipeline Configuration

The transition from local development to automated CI involves the creation of a .gitlab-ci.yml file. This file defines the stages of the pipeline, including build, test, and deployment.

Functional Testing and Database Interaction

A robust pipeline must include functional tests that interact with a real database to ensure the application behaves correctly in a production-like environment. This requires the configuration of a .env.test.local file and the orchestration of a test database.

The pipeline must be configured to create the test database and play the migrations before executing the tests. The phpunit section in the .gitlab-ci.yml file is updated to ensure that the environment is properly primed.

The testing process includes:

  • Editing the .env.test file to specify the test environment parameters.
  • Generating functional tests (e.g., FunctionalTest.php).
  • Executing the tests within the pipeline to validate the application's logic.

If any tests fail, the pipeline is configured to stop, preventing the deployment of broken code to the server.

Security Auditing with Symfony CLI

Integrating security checks into the CI/CD pipeline is a critical requirement for maintaining project integrity. The Symfony CLI provides a security checker command that can be run as a Docker image.

The manual execution of the security checker is as follows:

docker run --rm -v $(pwd):$(pwd) -w $(pwd) symfonycorp/cli check:security

However, integrating this into a .gitlab-ci.yml file presents challenges due to the image's entrypoint. A common failure occurs when the GitLab runner attempts to execute the script using sh, but the symfonycorp/cli image defines a custom entrypoint:

ENTRYPOINT ["/symfony"]

This results in the error Command "sh" does not exist. This error occurs because the image is designed to execute the /symfony binary directly rather than providing a shell environment.

Attempts to resolve this by putting /symfony in front of the command or resetting the entrypoint to an empty string in the YAML configuration often fail.

script:
- /symfony check:security

The failure indicates that the runner's attempt to inject a shell script is incompatible with the strict entrypoint of the Symfony CLI image.

Container Registry and Image Deployment

For projects requiring custom images, GitLab provides a container registry. To push a custom image to the registry, the developer must first authenticate.

docker login registry.gitlab.com

The Access Token used for this process must have specific permissions: read_api, read_registry, and write_registry, and it must have an expiration date.

The build and push process follows these steps:

  1. Build the image: docker build -t registry.gitlab.com/xyz/project.example.com .
  2. Push the image: docker push registry.gitlab.com/xyz/project.example.com

Once the image is in the registry, the pipeline can trigger a sequence of build, test, and deploy stages. Pushes to the main branch trigger the full sequence, while pushes to other branches only trigger the build and test phases.

Infrastructure Summary Table

Component Local Tooling CI/CD Equivalent Purpose
Runtime Docker / Podman GitLab Runner (Docker) Environment Consistency
Dependency Mgmt Composer Composer (in Container) Package Installation
Database MySQL (Docker) MySQL (Service Container) Data Persistence & Testing
Security Symfony CLI symfonycorp/cli Image Vulnerability Scanning
Deployment Manual / Local Deployer / GitLab CI Automated Production Release
Configuration .env .env.test / GitLab Variables Environment Management

Analysis of Pipeline Orchestration

The effectiveness of a Symfony GitLab CI pipeline depends on the seamless transition between the build, test, and deploy phases. The integration of a containerized PHP environment allows for high reproducibility, but it introduces complexities regarding entrypoints and volume mapping.

The "sh does not exist" error encountered with the symfonycorp/cli image highlights a fundamental conflict between GitLab's execution model (which expects a shell) and the specialized nature of some Docker images (which provide a single binary entrypoint). This necessitates a careful selection of images or the use of a wrapper image that provides both the necessary tools and a shell.

Furthermore, the use of a separate test database within the pipeline is non-negotiable for Symfony applications. Without it, functional tests cannot validate the interaction between the application and the persistence layer, leaving the project vulnerable to database-related regressions.

The deployment strategy, as seen in the integration with tools like Deployer, emphasizes a fail-safe approach. By ensuring that the deploy step is skipped if any previous test fails, the infrastructure maintains a high level of stability. The use of a container registry for custom images further optimizes this process by reducing the time spent on building images during every pipeline run.

In conclusion, the orchestration of Symfony within GitLab CI is an exercise in balancing the agility of containerized development with the rigor of automated testing. The success of such a system relies on the precise configuration of environment variables, the correct handling of container entrypoints, and the implementation of a strict pipeline hierarchy.

Sources

  1. GitLab Symfony CI
  2. GitLab Forum - Symfony CLI Issue
  3. CWD Blog - Docker PHP Symfony
  4. Ulrik - GitLab Pipeline PHP Deployer

Related Posts