Celery is three moving parts, not one
People search for "Celery hosting" expecting a single deploy target. Celery is actually a small system:
- 1A broker — Redis or RabbitMQ — that holds the task queue.
- 2Worker processes — long-running, always consuming from the broker.
- 3Optionally Celery Beat — a scheduler that enqueues periodic tasks.
- 4Optionally a result backend — Redis or your database — to store task results.
A good host has to run a *persistent worker process* (not a request/response function), give you a managed broker, and let you run Beat without two schedulers fighting. That's the lens for this comparison.
What to look for
- Long-running background processes with no HTTP route required.
- Managed Redis or RabbitMQ so you don't operate the broker.
- A separate process type for Beat (exactly one instance).
- Horizontal scaling of workers independent of your web app.
- Graceful shutdown so in-flight tasks finish on deploy.
- Logs and metrics to see queue depth and failures.
The options
Self-managed (VPS / Compose)
Run celery -A app worker under systemd or supervisor, Redis on the same box or a separate one. Cheap, total control, and entirely your responsibility for restarts, broker durability, and scaling. Fine for one box; painful as you grow.
Kubernetes
The "correct" answer at scale: a Deployment for workers, one for Beat (replicas: 1), KEDA to scale workers on queue depth, a managed Redis/RabbitMQ. Powerful and operationally heavy. Worth it when you have many queues and an SRE function.
Managed PaaS with worker process types
You define a web process and a worker process; the platform runs both. Add a managed Redis and you're done. Less control than k8s, far less YAML. The questions to ask: can it run a non-HTTP process, and does it offer a managed broker?
PandaStack
PandaStack runs Celery workers as container apps (a worker is just a container with a non-HTTP start command), provides managed Redis as the broker/result backend with the connection string injected, and supports cronjobs for scheduled work. For periodic tasks you can either run Celery Beat as its own container or use PandaStack cronjobs directly — often simpler than running Beat. Live logs come from self-hosted Elasticsearch.
Comparison
| Option | Worker process | Managed broker | Scheduler | Autoscale on queue | Ops effort |
|---|---|---|---|---|---|
| VPS/Compose | Yes (DIY) | DIY | DIY (Beat) | No | High |
| Kubernetes | Yes | Add-on | Beat Deployment | KEDA | High |
| Managed PaaS | Yes | Often | Beat/cron | Sometimes | Low |
| PandaStack | Yes | Managed Redis | Beat or cronjobs | Scale-to-zero on free | Low |
Deploying a Celery worker as a container
The trick is that a worker has no HTTP port — its "start command" is the Celery CLI. Same image as your web app, different command.
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# default command overridden per service
CMD ["celery", "-A", "proj", "worker", "--loglevel=info", "--concurrency=4"]Point Celery at the managed Redis via env:
# proj/celery.py
import os
from celery import Celery
app = Celery(
"proj",
broker=os.environ["REDIS_URL"],
backend=os.environ["REDIS_URL"],
)
app.conf.task_acks_late = True # redeliver if a worker dies mid-task
app.conf.worker_prefetch_multiplier = 1 # fair dispatch for long tasksRun multiple services from the same image by overriding the start command:
- Worker:
celery -A proj worker --loglevel=info --concurrency=4 - Beat (exactly one):
celery -A proj beat --loglevel=info
Beat vs. platform cronjobs
Celery Beat is a single process that must never be duplicated — two Beats means every scheduled task fires twice. If your platform offers native cronjobs, you can often skip Beat entirely: have the cronjob enqueue the task.
# cron_enqueue.py — run by a platform cronjob on a schedule
from proj.tasks import send_daily_digest
send_daily_digest.delay()This sidesteps the "only one Beat" footgun and gives you the schedule in the platform UI with its own logs. On PandaStack, a cronjob is a first-class object, so this pattern is clean.
Reliability checklist
task_acks_late = Trueso a crashed worker's task is redelivered.- Idempotent tasks — assume any task can run twice.
- Visibility timeout tuned above your longest task (Redis broker).
- Graceful shutdown — Celery handles
TERMby finishing in-flight tasks; give the platform a long enough termination grace period. - Monitor queue depth — a growing backlog means you need more workers (or scaling rules).
- Dead-letter handling — route failed tasks somewhere you'll actually see them.
My recommendation
At large scale with many queues, Kubernetes + KEDA is the right tool. For most teams, a managed platform with a worker process type and a managed Redis is the sweet spot — you get durable queues and independent scaling without writing operators. PandaStack covers this with container workers, managed Redis, and native cronjobs for scheduling; just note free-tier scale-to-zero means a cold start when the queue has been idle.
References
- Celery user guide: https://docs.celeryq.dev/en/stable/userguide/index.html
- Celery Beat (periodic tasks): https://docs.celeryq.dev/en/stable/userguide/periodic-tasks.html
- Redis as a broker: https://docs.celeryq.dev/en/stable/getting-started/backends-and-brokers/redis.html
- KEDA Celery/Redis scalers: https://keda.sh/docs/latest/scalers/redis-lists/
- Celery task reliability (acks_late): https://docs.celeryq.dev/en/stable/userguide/configuration.html#task-acks-late
---
Need durable background workers with a managed Redis broker and scheduled tasks? PandaStack's free tier runs container workers, Redis, and cronjobs side by side. Start at https://dashboard.pandastack.io