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.

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