Laravel Horizon not executing pending jobs - Kubernetes and Docker environment

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company
# Laravel Horizon Stuck: Why Pending Jobs Fail in Kubernetes/Docker Deployments As a senior developer working with containerized applications, we often encounter subtle but frustrating issues when deploying complex systems like Laravel applications onto orchestrators such as Kubernetes. One common scenario involves splitting the application into microservices—a web server pod and a background worker pod for queue processing (like Laravel Horizon). This post dives deep into a specific problem: why Laravel Horizon jobs remain in the 'Pending' state in the dashboard, even when the environment seems correctly configured, and how to resolve this discrepancy between manual execution and automated execution in a Docker/Kubernetes setup. ## The Scenario: A Split Pod Architecture Imagine your deployment consists of two distinct pods within Kubernetes: 1. **Web Pod:** Running Apache or Nginx, serving the application on port 80. 2. **Worker Pod:** Running the Laravel Horizon worker command (`php artisan horizon`). You have confirmed that both pods successfully connect to the same Redis instance and share the correct environment prefix for Horizon—all checks pass. Yet, when you view the Horizon dashboard, jobs are listed as 'Active' or 'Pending', but they never transition to 'Processing' or 'Completed.' The crucial observation is what happens when you manually SSH into the web pod and run `php artisan horizon`: the jobs execute immediately and successfully. This immediately points the finger toward an environmental difference or process management issue within the containerized environment itself, rather than a flaw in the Laravel code or the Redis setup. ## The Diagnosis: Process Isolation and Entrypoints The core issue usually lies not in *what* command is executed, but *how* that command is executed as the primary process within the Docker container. In a Kubernetes environment, each pod operates in isolation, and how you define the entry point (`CMD` or `ENTRYPOINT`) dictates what the container actually runs when it starts up. When you run `php artisan horizon` manually via SSH, you are executing it directly in the host shell context, which has full access to the system environment variables and processes. However, when Kubernetes launches the worker pod, if the process isn't managed correctly, or if the container exits immediately after starting the command, the job processing loop stalls. ### The Root Cause: Stalled Worker Process In many Docker/Kubernetes setups for background workers, developers often rely on simple `CMD` instructions that execute a single command and then exit, assuming an external process manager will handle persistence. If Horizon is run as a standalone CLI command inside the container without being managed by a supervisor or a dedicated process manager designed for long-running tasks, it might execute its initial setup but fail to maintain the continuous polling loop necessary for job execution. The fact that manual execution works proves the environment *can* run the command; the failure lies in the automated, persistent execution expected by the scheduler. ## Solutions: Ensuring Persistent Worker Execution To solve this, we need to ensure that the Horizon worker process is treated as a long-running service within the container, managed by the operating system itself, rather than just a single-shot command. ### 1. Use a Process Manager (Recommended) The most robust solution is to use a dedicated process manager like **Supervisor** or configure your Dockerfile entrypoint to explicitly run the command in an infinite loop, ensuring it stays alive and actively polling Redis for new jobs. **Example using a simple loop (for demonstration):** Instead of just running `php artisan horizon`, you need a persistent script: ```bash #!/bin/sh # Entrypoint script for the worker pod while true; do echo "Checking for Horizon jobs..." php artisan horizon --queue=default --timeout=60 --tries=3 sleep 60 # Wait before checking again done ``` You would then set your container's `CMD` or `ENTRYPOINT` to execute this script. This ensures the PHP process remains active, constantly polling Horizon for pending jobs, which is exactly what a background worker needs to do reliably in a container environment. This approach aligns with best practices when deploying services built on frameworks like Laravel, as detailed by the team at [laravelcompany.com](https://laravelcompany.com). ### 2. Review Container Permissions While less likely if manual execution succeeds, always verify that the user running the process inside the container has the necessary permissions to read Redis and write logs within the volume mount structure defined by Kubernetes. Misconfigured `securityContext`