
August 20, 2026
10 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
If you are running multiple Laravel applications, background queue workers, or PHP-FPM services across several servers, managing deployments with basic scripts quickly becomes unmanageable. Nomad: Simple Workload Orchestration solves this by providing a single control plane to schedule, deploy, and heal containerized or binary workloads without the massive operational overhead of Kubernetes. For full-stack developers and agencies maintaining client infrastructure, understanding this tool bridges the gap between manual server management and enterprise-grade platform engineering.
While my primary focus remains on application architecture and Laravel development in Nepal, I frequently encounter projects where the deployment target has outgrown a single VPS but does not justify a dedicated DevOps team. In these scenarios, adopting a complex orchestrator often introduces more problems than it solves. Nomad occupies a critical middle ground. It allows you to treat your fleet of Ubuntu servers as a unified resource pool, scheduling PHP workers, Nginx reverse proxies, and Redis instances based on actual capacity rather than static assignments. This approach aligns perfectly with the pragmatic engineering philosophy required when delivering cost-effective web solutions for businesses that need reliability without cloud-native bloat.
What makes Nomad: Simple Workload Orchestration different from Kubernetes?
The distinction lies in architectural philosophy and operational weight. Kubernetes is a comprehensive platform designed to be an operating system for the cloud; it mandates containers, requires extensive CRDs, and runs dozens of internal components just to reach a ready state. Nomad is fundamentally a scheduler first. It supports containers via Docker/Podman but also treats raw binaries, Java JARs, QEMU VMs, and simple shell scripts as first-class citizens. This "multi-workload" capability is decisive for PHP shops where legacy applications may not yet be containerized or where specific compliance requirements demand bare-metal execution alongside modern microservices.
In practice, this means a three-node Nomad cluster can run comfortably on modest VPS instances (e.g., 2 vCPU / 4GB RAM each), whereas a minimally viable Kubernetes cluster often demands significantly more resources just for its control plane. For agencies billing in NPR or serving cost-sensitive markets, this efficiency translates directly to healthier project margins. Nomad also integrates natively with other HashiCorp tools like Consul for service discovery and Vault for secrets management, but unlike K8s, these remain optional external dependencies rather than hard-coded prerequisites.
How do you configure a Laravel job spec in Nomad?
Defining a workload in Nomad uses HCL (HashiCorp Configuration Language). A common mistake I see when developers transition from Docker Compose is trying to map services one-to-one without considering Nomad's "Job → Group → Task" hierarchy. A Job represents the entire application (e.g., "laravel-ecommerce"), a Group defines co-located tasks that share networking and lifecycle (e.g., "web-worker-pool"), and Tasks are the individual units of execution.
Basic Laravel Web Job Specification
Below is a production-viable starting point for a Laravel 12 application running on PHP 8.4 FPM behind Nginx. Note the explicit resource stanza; Nomad schedules based on reserved capacity, not observed usage, which prevents noisy-neighbor issues common in oversold environments.
job "laravel-shop" {
datacenters = ["dc1"]
type = "service"
group "app" {
count = 3
network {
port "http" { to = 8080 }
}
task "nginx-php" {
driver = "docker"
config {
image = "registry.example.com/shop:php8.4-v2.1.0"
ports = ["http"]
volumes = [
"local/storage:/var/www/html/storage/app/public"
]
}
env {
APP_ENV = "production"
CACHE_DRIVER = "redis"
QUEUE_CONNECTION = "redis"
}
resources {
cpu = 500
memory = 512
}
service {
name = "laravel-shop-web"
port = "http"
check {
type = "http"
path = "/up"
interval = "10s"
timeout = "2s"
}
}
}
}
} Critical details for PHP practitioners: Always mount persistent storage explicitly if your app writes to disk (uploads, logs). Nomad tasks are ephemeral by default. Use the /up endpoint (available in Laravel 11+) for health checks instead of / to avoid triggering heavy middleware or database queries on every probe. Define environment variables via Vault templates or Nomad variables in production rather than hardcoding them in HCL files committed to Git.
Scheduling Queue Workers Separately
A pattern I have found reliable on real client projects is separating web processes from queue workers into distinct groups within the same job file. This allows independent scaling: you might need 10 web instances during peak traffic but only 3 dedicated queue workers for email processing. Workers should use the batch or system job type depending on whether they are long-running daemons (queue:work --daemon) or triggered batch processors.
When should you choose Nomad over Docker Compose or K8s?
Choosing the right orchestration layer is an architectural decision with long-term maintenance implications. The choice rarely comes down to technical superiority alone; it involves team expertise, budget, and operational maturity. Below is a comparison framework grounded in actual deployment experience rather than marketing matrices.
| Criteria | Docker Compose | Nomad | Kubernetes |
|---|---|---|---|
| Cluster Support | Single node only | Multi-datacenter native | Multi-zone/cloud native |
| Workload Types | Containers only | Containers, binaries, VMs, scripts | Containers primarily (VMs via KubeVirt) |
| Learning Curve | Low (hours) | Moderate (days to weeks) | High (months to proficiency) |
| Min Control Plane RAM | N/A (app only) | ~100–200 MB | ~2–4 GB minimum |
| Service Discovery | DNS aliases only | Native + Consul integration | CoreDNS + Ingress controllers |
| Best For | Local dev, single-server prod | Small-medium fleets, mixed workloads | Large-scale cloud-native platforms |
Docker Compose remains excellent for local development and single-server staging environments. However, once you need high availability across multiple physical hosts or regions, Compose hits a hard wall. Nomad fills this gap precisely. If your team already understands Linux fundamentals, systemd, and basic networking—as most experienced full-stack developers do—the transition to Nomad feels like a natural extension of existing skills rather than a paradigm shift. Kubernetes becomes justified when you have dedicated platform engineers, require auto-scaling based on custom metrics at massive scale, or operate within a cloud ecosystem that provides managed K8s at reasonable cost.
How does Nomad handle service discovery and load balancing for PHP apps?
Service discovery is where many orchestration tutorials fail to address real-world PHP needs. Unlike Node.js or Go services that often embed their own HTTP routers, PHP-FPM typically sits behind Nginx or Caddy. Nomad integrates tightly with Consul to register healthy task instances automatically. When your Laravel job specifies a service block, Nomad registers each running container with Consul, including host IP and dynamically assigned port.
Integrating with Nginx Upstreams
Rather than hardcoding backend IPs, configure Nginx to query Consul DNS or use a template rendered by consul-template. A practical pattern I have used on legal-tech portals involves running an Nginx sidecar or gateway group that watches Consul for laravel-shop-web service changes and regenerates upstream configuration automatically. This eliminates manual reloads during deployments and ensures zero-downtime rollouts.
# Example consul-template snippet for nginx upstream
{{ range service "laravel-shop-web" }}
server {{ .Address }}:{{ .Port }} max_fails=3 fail_timeout=30s;
{{ end }} This approach keeps your routing layer decoupled from application deployment cycles. Health checks defined in the Nomad job spec drive registration: if a Laravel container fails its /up check three times consecutively, Consul deregisters it, and Nginx stops sending traffic within seconds. This feedback loop is essential for maintaining uptime during rolling updates or when individual nodes experience hardware degradation.
What are the operational gotchas when running Nomad in production?
Theory differs from practice. After deploying Nomad across various client environments, certain recurring issues emerge that documentation glosses over. Addressing these proactively prevents 2 AM debugging sessions.
- Disk Exhaustion from Docker Images: Nomad does not garbage-collect unused Docker images by default. On servers with limited disk (common on budget VPS plans), old image layers accumulate rapidly. Configure the Docker driver's
gc.image_delayparameter or implement a cron job runningdocker system pruneweekly. I typically set retention to 7 days on production clients. - Resource Overcommitment Risks: Nomad enforces hard limits only when cgroups are properly configured. Without explicit CPU/memory reservations in job specs, a single runaway PHP worker can starve neighboring tasks. Always define
resourcesblocks conservatively and monitor actual usage via Nomad metrics or Telemetry before increasing allocations. - Persistent Volume Management: Nomad’s CSI support exists but is less mature than Kubernetes'. For Laravel storage, prefer host volumes with careful permission management or object storage (S3-compatible) for media files. Bind-mounting host paths works reliably but ties tasks to specific nodes unless you implement shared filesystems like NFS—which adds its own complexity.
- Secret Sprawl Prevention: Storing database passwords in HCL files defeats the purpose of orchestration. Use Nomad Variables (built-in since 1.4) or Vault integration from day one. Migrating secrets later requires rewriting every job spec and rotating credentials simultaneously—a painful process best avoided.
- Client Node Drain Procedures: When decommissioning a server, always run
nomad node drain -enable -yes <node-id>before shutting down. This gracefully migrates allocations to other nodes respecting health checks. Simply killing the agent causes abrupt task termination and potential data corruption in stateful services.
Monitoring deserves special emphasis. Nomad exposes metrics via Prometheus format at /v1/metrics. Integrate this early with Grafana dashboards tracking allocation placement failures, resource saturation, and job update durations. Reactive troubleshooting after users report slowness is far more expensive than proactive alerting on scheduling latency spikes. For teams managing DevOps automation, embedding observability into the initial cluster setup pays dividends throughout the system's lifetime.
Implementing Nomad: Simple Workload Orchestration for Your Stack
Adopting Nomad: Simple Workload Orchestration represents a pragmatic middle path for PHP and Laravel teams who have outgrown single-server deployments but cannot justify Kubernetes' operational tax. Start small: migrate non-critical batch jobs or staging environments first to build team familiarity. Validate your Docker images locally with identical drivers before pushing to production. Document your job specifications as code in version control alongside application repositories. Most importantly, resist the urge to adopt features simply because they exist; Nomad's strength lies in doing fewer things exceptionally well. If your current pain points involve coordinating deployments across 3–20 servers, handling mixed workload types, or reducing infrastructure costs while maintaining professional reliability, Nomad deserves serious evaluation. For tailored guidance on implementing orchestrated deployments for your specific Laravel or PHP architecture, reach out to discuss your project requirements.

