How can I run background tasks in Gitlab CICD?
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
How Can I Run Background Tasks in GitLab CI/CD? Solving the Perpetual Wait Problem
As developers integrating complex services into Continuous Integration/Continuous Delivery (CI/CD) pipelines, we frequently encounter a common hurdle: how to manage long-running background processes within ephemeral job executors like GitLab Runners. Specifically, when you try to launch service-based commands—such as queue listeners or persistent daemons—you often run into the issue of perpetual waiting, causing your pipeline to hang indefinitely.
This post will dive deep into why this happens and provide robust, practical solutions for effectively running background tasks in your GitLab CI/CD workflows, particularly when dealing with PHP/Laravel applications.
The Problem: Why Backgrounding Fails in CI/CD
You've encountered the classic scenario: you want to execute a command like php artisan queue:listen --timeout=0 & within your .gitlab-ci.yml, but the job stalls, waiting for this process to finish, even though it’s running in the background.
The reason this fails is rooted in how GitLab Runners manage job execution. A CI job operates under a specific shell context. When you use the ampersand (&) to put a command in the background, that command runs, but the parent shell script (the one executing the runner logic) still waits for any associated processes to complete before declaring the entire job successful or failing. Furthermore, methods like nohup often fail because they manage process output redirection but don't fundamentally change how the CI system monitors the primary execution flow.
In essence, the CI environment expects the main script to terminate cleanly. If a long-running worker is executed directly within the job steps, GitLab interprets this as an incomplete build, leading to the perceived "perpetual wait."
Solutions for Running Background Tasks Effectively
Running persistent services in a CI context requires shifting your mindset from running a single task to managing a process lifecycle. Here are the most effective strategies:
1. The Container-Native Approach (Recommended)
The most idiomatic and robust way to handle long-running tasks in containerized environments like Docker (which GitLab CI heavily relies on) is to use the container itself as the service, or manage the process explicitly within the container’s lifecycle.
Instead of trying to daemonize a command inside the job step, you should treat the job as the initializer. If your goal is simply to ensure the worker process is running and accessible, focus on how that process interacts with the environment.
For persistent services in Laravel applications, instead of using queue:listen directly inside the build stage, consider structuring your CI to handle deployment artifacts, and let the actual queue workers run persistently outside the ephemeral job context (e.g., managed by Supervisor or systemd, which is standard practice for production deployments).
2. Using Process Managers within the Job (For Testing/Staging)
If you absolutely must test an execution flow during a CI run, use tools designed to manage foreground and background processes within the shell context, such as screen or tmux. While this provides session persistence, it still doesn't solve the problem of the GitLab Runner waiting for the parent command.
A better approach, specific to PHP/Laravel workflows where you are testing a setup phase, is to use an explicit mechanism that signals completion without blocking the runner indefinitely.
3. Decoupling Build and Service Start (The CI Best Practice)
For most CI scenarios, the best practice is to decouple the build process from the service execution. Your job should focus on building artifacts, testing code, and ensuring all necessary files are present. The actual long-running workers should be started separately, often post-deployment or via a dedicated orchestration tool.
If you must initiate a process during CI for debugging (e.g., ensuring the queue listener starts correctly before deployment), ensure that the command itself is designed to exit successfully after starting the desired service, rather than running indefinitely in the foreground.
Practical GitLab CI Example
For a scenario where you need to execute a setup command and then immediately exit successfully, treating the background process as an external dependency is cleaner. However, if you are using Docker, the best practice involves ensuring your container starts the necessary services:
stages:
- build
- deploy
build_and_start_workers:
stage: build
image: php:8.2-cli
script:
# 1. Run necessary build steps (e.g., composer install, asset compilation)
- composer install --no-dev --optimize-autoloader
- php artisan cache:clear
# 2. Start the queue listener in a way that signals completion immediately.
# NOTE: For true background services, this approach is usually handled by
# external process managers (like Supervisor), but for CI testing, we ensure
# the command executes and exits cleanly if possible.
- echo "Starting queue listener setup..."
# If running a short test command that sets up the environment:
- php artisan queue:work --timeout=1 # Run one job instead of listening perpetually
# If you need to run an actual long-running service, it should be deployed
# as a separate persistent container or managed by systemd post-deployment.
Conclusion
Running background tasks in GitLab CI/CD is less about shell redirection (& or nohup) and more about understanding the lifecycle of your execution environment. For complex, long-running services like queue listeners in a Laravel application, relying on the ephemeral nature of a CI job to host persistent processes is fundamentally flawed.
The senior developer approach is to separate concerns: let the CI pipeline handle building and testing, and let dedicated process managers (like Supervisor or Kubernetes) handle the persistent operation of your background workers in the production environment. This separation ensures reliability, predictability, and makes your pipelines scalable, aligning with robust architectural principles often seen in modern PHP deployments found on platforms like Laravel Company.