Choosing Process Topology for Scalingo Apps That Serve HTTP and Run Background Jobs
A decision guide for structuring Scalingo applications that serve HTTP and process background jobs: use separate web and worker process types, declare them in a Procfile, scale independently, and validate in staging with concrete test steps.
06 Dec 2025, 08:49 UTC

The Problem
You have a Scalingo application that must serve HTTP requests and process background jobs. The platform runs each declared process type in its own dyno, so the way you declare and scale those types determines whether a traffic spike starves your workers, whether a deployment restarts in-flight jobs, and how much you pay for idle capacity.
Decision and Constraints
Use separate web and worker process types. Keep HTTP serving in web dynos and queue consumption in worker dynos. Start with one of each, then scale independently based on observed demand rather than pre-provisioning redundancy.
Constraints to honor:
- Each dyno type scales and restarts independently.
- A single worker dyno provides one execution slot, not high availability.
- Queue connectivity (Redis) must be validated separately from application code.
- Secrets belong in environment configuration, never in the Procfile.
Supported Options Compared
| Option | Benefits | Costs and Risks |
|---|---|---|
| Web-only with inline jobs | Fewest dynos; simplest Procfile | Request latency and job failures become coupled; restarts interrupt in-flight work; CPU-heavy jobs degrade HTTP throughput |
| Web + worker (recommended) | Independent scaling and restart behavior; HTTP capacity changes don't restart workers | Additional dyno cost; requires queue operations and monitoring |
| Multiple web + worker | More concurrency and failover headroom for HTTP | Higher cost; greater capacity-management complexity; still only one worker unless you scale it |
Trade-offs
The core trade-off is isolation versus resource consumption. A dedicated worker prevents CPU-heavy or I/O-bound jobs from degrading request latency, but it adds a continuously running dyno (roughly €0.03–0.06/hour for an S/M container at time of writing) and requires queue monitoring, bounded concurrency, and idempotent job handlers.
If you run jobs inline in the web process, a deployment that restarts web dynos also kills any executing jobs. With separate types, scalingo restart worker or a worker-only scale event leaves web dynos untouched, and vice versa.
Concrete Implementation
1. Declare Process Types in Procfile
Create a Procfile at the repository root. For a Ruby on Rails app using Puma and Sidekiq:
web: bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq -C config/sidekiq.yml
The worker requires a reachable Redis-compatible endpoint exposed through REDIS_URL. Set that in the Scalingo dashboard under Environment Variables or via CLI:
# Run from your workstation with Scalingo CLI authenticated
scalingo --app YOUR_APP env-set REDIS_URL=redis://:password@host:port/db
Where to run: Local terminal with scalingo CLI installed and logged in (scalingo login).
Permissions: App collaborator with write access.
Risk: Committing REDIS_URL to source control leaks credentials. Use the CLI or dashboard only.
2. Deploy and Verify Process Types Start
# Deploy from repository root
git push scalingo main
# or
scalingo deploy
After deploy, confirm both types are running:
scalingo --app YOUR_APP ps
# Expected output shows web and worker lines with "up" status
3. Scale Independently
Adjust each type based on its own metrics:
# Scale web to 2 dynos for HTTP concurrency
scalingo --app YOUR_APP scale web:2
# Scale worker to 3 dynos for queue throughput
scalingo --app YOUR_APP scale worker:3
Check the new counts:
scalingo --app YOUR_APP ps
Validation in Staging
Before promoting to production, run this validation sequence in a staging app that mirrors the production Procfile and environment.
- Deploy staging with the two Procfile entries and verify
psshows both types up. - Enqueue a uniquely identifiable test job (e.g., a Sidekiq job that logs a UUID and sleeps 30 seconds).
- Confirm from logs that a worker dyno, not a web dyno, consumes it:
scalingo --app STAGING_APP logs --filter worker | grep YOUR_UUID - Run repeated HTTP health requests while the test job executes, recording latency (e.g., with
heyorwrk). Verify p99 latency stays within your threshold (e.g., < 200 ms). - Change worker capacity (
scale worker:2thenworker:1) and confirm job concurrency changes without materially affecting HTTP latency. - Restart the worker (
scalingo restart worker) and confirm queued work resumes. If your job handler is idempotent, a repeated execution is safe.
Job Design Requirements
Separate processes only help if jobs are written correctly:
- Idempotency: Design handlers so re-execution after a restart or crash produces the same outcome.
- Acknowledgment after durable completion: Only ack the message (or mark job done) after the database transaction commits or the external API call succeeds.
- Finite timeouts: Set execution timeouts in Sidekiq (
config/sidekiq.ymltimeout) and at the Redis/client level so stuck jobs don't block the slot indefinitely. - Dead-letter visibility: Route failed jobs to a separate queue or table with alerting so they don't silently disappear.
Limitations and Ongoing Verification
- One worker dyno is one execution slot. It becomes unavailable during maintenance, deployment, or restart. For production HA, run at least two worker dynos and ensure your queue and job design tolerate concurrent consumers.
- Scaling web dynos does not automatically add worker capacity. Monitor HTTP latency, queue depth, job age, and worker failure rates independently.
- Queue endpoint reachability (network, TLS, auth, connection pool) must be validated separately. A misconfigured
REDIS_URLwill show as worker crashes in logs. - Exact CLI syntax and Procfile parsing details can change. Confirm against current Scalingo documentation before production use.
Practical Check After Changes
After any Procfile edit, scale event, or environment change, run:
scalingo --app YOUR_APP ps
scalingo --app YOUR_APP logs --lines 100
Look for both process types in "up" state and no repeated crash/restart loops in the worker logs.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.