Table of Contents
- Introduction
- What Is Celery?
- Architecture Overview
- Installation & First‑Time Setup
- Basic Usage: Defining and Running Tasks
- Choosing a Broker and Result Backend
- Task Retries, Time Limits, and Error Handling
- Periodic Tasks & Celery Beat
- Monitoring & Management Tools
- Scaling Celery Workers
- Best Practices & Common Pitfalls
- Advanced Celery Patterns (Canvas, Groups, Chords)
- Deploying Celery in Production (Docker & Kubernetes)
- Security Considerations
- Conclusion
- Resources
Introduction
In modern web applications, background processing is no longer a luxury—it’s a necessity. Whether you need to send email confirmations, generate PDF reports, run machine‑learning inference, or process large data pipelines, handling these tasks synchronously would cripple user experience and waste server resources. Celery is the de‑facto standard for implementing asynchronous, distributed task queues in Python.
This article is a comprehensive, in‑depth guide that walks you through Celery from the ground up, covering its core concepts, practical implementation details, production‑grade deployment strategies, and advanced patterns. By the end, you’ll be equipped to design, develop, and operate robust Celery‑based systems on any scale.
What Is Celery?
Celery is an open‑source asynchronous task queue/job queue based on distributed message passing. It allows you to:
- Offload time‑consuming work from request‑handling threads or processes.
- Scale horizontally by adding more worker processes or machines.
- Schedule recurring jobs (cron‑style) or delayed executions.
- Track task state and retrieve results via a result backend.
Key design goals behind Celery:
| Goal | Why It Matters |
|---|---|
| Simplicity | Minimal boilerplate to define a task; a single line decorator does the job. |
| Reliability | Guarantees at‑least‑once delivery, supports ACK/NACK semantics, and can survive broker restarts. |
| Flexibility | Works with many brokers (RabbitMQ, Redis, Amazon SQS, Kafka, etc.) and result backends (Redis, PostgreSQL, MongoDB, etc.). |
| Extensibility | Canvas primitives (chains, groups, chords) enable complex workflows. |
| Observability | Tools like Flower, Prometheus exporters, and built‑in events make monitoring straightforward. |
Architecture Overview
Understanding Celery’s architecture is crucial before diving into code. Below is a high‑level diagram and description of each component.
+----------------+ +-----------------+ +-----------------+
| Producer | ---> | Broker (e.g., | ---> | Worker(s) |
| (Web/CLI/API) | | RabbitMQ) | | (Celery Worker)|
+----------------+ +-----------------+ +-----------------+
| |
| v
| +-----------+
| | Task |
| | Execution|
| +-----------+
| |
| v
| +-----------+
| | Result |
| | Backend |
| +-----------+
- Producer – Any Python process that calls
task.delay()ortask.apply_async(). Typically your web server (Flask, Django, FastAPI) or a CLI script. - Broker – A message transport that stores task messages until a worker can consume them. The broker guarantees delivery semantics (e.g., RabbitMQ uses AMQP, Redis uses pub/sub + list).
- Worker – A long‑running process (
celery -A proj worker) that pulls tasks from the broker, deserializes them, executes the Python function, and optionally stores results. Workers can be multithreaded (-P eventlet,-P gevent) or multiprocess (prefork– the default). - Result Backend – Optional storage for task return values and state (
SUCCESS,FAILURE,RETRY). EnablesAsyncResultobjects for later retrieval.
The communication flow:
- Task Submission – Producer serializes the task (function name, args, kwargs) and publishes it to a queue on the broker.
- Task Dispatch – Broker routes the message to a worker’s queue (based on routing keys, exchanges, etc.).
- Execution – Worker acknowledges receipt, runs the task, and updates the result backend.
- Result Retrieval – Producer (or any client) can query the backend using the task ID.
Installation & First‑Time Setup
System Requirements
| Component | Minimum Version | Recommended |
|---|---|---|
| Python | 3.8 | 3.11+ |
| pip | 21.0 | Latest |
| Broker | — | RabbitMQ 3.9+ or Redis 6+ |
| OS | Any (Linux, macOS, Windows) | Linux for production |
Installing Celery
# Create a virtual environment (recommended)
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install Celery with a broker of choice
pip install "celery[redis]" # Includes Redis integration
# or
pip install "celery[rabbitmq]" # Includes kombu's RabbitMQ support
Note: Celery itself does not ship a broker. You must install and run Redis, RabbitMQ, or another supported broker separately.
Creating a Minimal Project
my_celery_app/
├── celery_app.py
├── tasks.py
└── requirements.txt
celery_app.py
from celery import Celery
# The first argument is the name of the current module.
# The broker URL points to Redis running locally.
app = Celery('my_celery_app',
broker='redis://localhost:6379/0',
backend='redis://localhost:6379/1')
# Optional: load configuration from a separate module
app.config_from_object('celery_config')
celery_config.py (optional)
# Example configuration
task_serializer = 'json'
result_serializer = 'json'
accept_content = ['json']
timezone = 'UTC'
enable_utc = True
task_acks_late = True # Acknowledge only after task completes
worker_prefetch_multiplier = 1
tasks.py
from .celery_app import app
@app.task
def add(x, y):
"""Simple addition task."""
return x + y
@app.task(bind=True, max_retries=3, default_retry_delay=5)
def unreliable_task(self, payload):
"""Demonstrates retry handling."""
try:
# Imagine a flaky external API call here
if payload % 2 == 0:
raise ValueError("Simulated failure")
return f"Processed {payload}"
except Exception as exc:
# Retry the task after a delay
raise self.retry(exc=exc)
Running the Worker
celery -A my_celery_app.celery_app worker --loglevel=INFO
You should see the worker start and wait for tasks.
Submitting a Task from Python REPL
>>> from my_celery_app.tasks import add, unreliable_task
>>> result = add.delay(4, 7)
>>> result.id
'c8c2f9f7-8d3b-4e33-9b5c-6c8a6c6d2d9f'
>>> result.get(timeout=10) # Blocks until result is ready
11
That’s the simplest possible Celery workflow.
Choosing a Broker and Result Backend
Celery’s flexibility comes from its broker abstraction (via Kombu) and pluggable result backends. Selecting the right combination depends on latency, durability, scaling, and operational constraints.
Popular Brokers
| Broker | Strengths | Weaknesses | Typical Use‑Case |
|---|---|---|---|
| RabbitMQ | Strong delivery guarantees, complex routing (exchanges, topics), high throughput, clustering support. | Requires more operational knowledge, heavier memory footprint. | Enterprise workloads with strict QoS, need for priority queues. |
| Redis | Simple setup, in‑memory speed, supports result backend out‑of‑the‑box. | Data loss on crash unless persistence enabled; not ideal for massive message durability. | Small‑to‑medium workloads, dev/test, low‑latency tasks. |
| Amazon SQS | Fully managed, horizontal scaling, pay‑as‑you‑go. | No native support for task result backend; limited message size; eventual consistency. | Cloud‑native apps on AWS, where operational overhead must be minimal. |
| Kafka | Very high throughput, log‑based storage, replayability. | Higher latency for small tasks, requires careful consumer offset handling. | Event‑driven pipelines, streaming analytics. |
Result Backend Options
| Backend | Pros | Cons | When to Use |
|---|---|---|---|
| Redis | Fast, easy to configure, works with same instance used as broker. | Volatile unless persistence enabled; limited query capabilities. | Simple result storage, short‑lived tasks. |
| PostgreSQL | Durable, relational queries, supports large payloads. | Slower than Redis; requires ORM or raw SQL handling. | Enterprise environments needing audit trails. |
| MongoDB | Schema‑flexible, good for storing large JSON payloads. | Slightly higher latency; additional operational component. | Data‑centric pipelines where results are consumed by other services. |
| Cassandra | Highly scalable, fault‑tolerant. | Complex setup; eventual consistency. | Massive scale, multi‑region deployments. |
| None | No storage overhead. | AsyncResult cannot retrieve result; only fire‑and‑forget. | Stateless fire‑and‑forget jobs (e.g., logging). |
Configuration Example (RabbitMQ + PostgreSQL)
# celery_config.py
broker_url = 'amqp://guest:guest@localhost:5672//'
result_backend = 'db+postgresql://user:pwd@localhost/celery_results'
task_serializer = 'json'
result_serializer = 'json'
accept_content = ['json']
timezone = 'UTC'
enable_utc = True
Basic Usage: Defining and Running Tasks
The @app.task Decorator
The most common way to create a Celery task is to decorate a regular Python function.
@app.task
def send_welcome_email(user_id):
"""Background email sender."""
user = get_user_from_db(user_id)
email_body = render_template('welcome.html', user=user)
smtp_send(user.email, "Welcome!", email_body)
return f"Email sent to {user.email}"
Key arguments:
| Argument | Description |
|---|---|
bind=True | Passes the task instance (self) as the first argument, enabling retries, request information, etc. |
name='custom.name' | Overrides the default task name (module.function). Useful for backward compatibility. |
max_retries | Maximum retry attempts before giving up. |
default_retry_delay | Seconds to wait before retrying. |
rate_limit='10/m' | Throttles the task to 10 executions per minute. |
soft_time_limit / time_limit | Soft limit raises SoftTimeLimitExceeded inside the task; hard limit terminates the worker process. |
Synchronous vs Asynchronous Invocation
| Method | Description | Blocking? |
|---|---|---|
task.delay(*args, **kwargs) | Shortcut for apply_async with default options. | No |
task.apply_async(args=..., kwargs=..., countdown=10, eta=dt, retry=True, ...) | Full control over routing, countdown, expiry, and more. | No |
task.apply(*args, **kwargs) | Executes locally, bypassing broker (useful for testing). | Yes |
Example: Scheduling a task for later execution
# Run 5 minutes from now
process_report.apply_async(args=[report_id], countdown=300)
# Run at a specific datetime (UTC)
process_report.apply_async(args=[report_id], eta=datetime(2026, 4, 1, 12, 0, tzinfo=timezone.utc))
Accessing Task Metadata
Every task receives a self object when bind=True. It provides:
self.request.id– Unique task identifier.self.request.retries– Current retry count.self.request.delivery_info– Broker routing details.self.request.is_eager– True when called withapply(synchronous).
@app.task(bind=True, max_retries=5)
def fetch_data(self, url):
try:
response = http_get(url, timeout=5)
response.raise_for_status()
return response.json()
except Exception as exc:
# Exponential back‑off: 2^retries seconds
delay = 2 ** self.request.retries
raise self.retry(exc=exc, countdown=delay)
Task Retries, Time Limits, and Error Handling
Robust background processing requires graceful handling of failures. Celery offers built‑in mechanisms.
Automatic Retries
Use self.retry() inside the task. You can also set a default retry policy in the configuration:
# celery_config.py
task_default_retry_delay = 60 # seconds
task_annotations = {
'my_app.tasks.unreliable_task': {'max_retries': 3},
}
Time Limits
- Soft Time Limit – Raises
SoftTimeLimitExceededinside the task, allowing cleanup. - Hard Time Limit – Worker process is terminated if the task exceeds this limit.
@app.task(soft_time_limit=30, time_limit=35)
def long_running():
try:
for i in range(100):
time.sleep(1) # Simulate work
except SoftTimeLimitExceeded:
logger.warning("Task exceeded soft limit, cleaning up")
# Perform partial rollback or cleanup
raise
Custom Error Handlers
Celery signals let you hook into task lifecycle events.
from celery.signals import task_failure, task_success
@task_failure.connect
def task_failure_handler(sender, task_id, exception, args, kwargs, **kw):
logger.error(f"Task {sender.name}[{task_id}] failed: {exception}")
@task_success.connect
def task_success_handler(sender, result, **kw):
logger.info(f"Task {sender.name} succeeded with result: {result}")
Dead Letter Queues (DLQ)
When a task exceeds its max retries, you can route it to a dead letter queue for later inspection.
# In rabbitmq_config.py
task_routes = {
'my_app.tasks.unreliable_task': {
'queue': 'default',
'routing_key': 'default',
'delivery_mode': 2, # persistent
},
'my_app.tasks.dead_letter': {
'queue': 'dead_letter',
},
}
Then, in the task:
@app.task(bind=True, max_retries=2, default_retry_delay=5, acks_late=True)
def unreliable_task(self, data):
try:
# Process data
return do_work(data)
except Exception as exc:
if self.request.retries >= self.max_retries:
# Send to dead letter
self.apply_async(args=[data], queue='dead_letter')
raise
raise self.retry(exc=exc)
Periodic Tasks & Celery Beat
Celery includes a scheduler called Celery Beat that dispatches tasks at regular intervals, similar to cron.
Defining a Periodic Task
# celery_config.py
beat_schedule = {
'send-daily-report': {
'task': 'my_app.tasks.send_daily_report',
'schedule': crontab(hour=7, minute=30), # 07:30 UTC every day
'args': (),
},
'cleanup-temp-files': {
'task': 'my_app.tasks.cleanup_temp',
'schedule': timedelta(hours=6), # Every 6 hours
},
}
Running Beat
celery -A my_celery_app.celery_app beat --loglevel=INFO
You can combine Beat with a worker in a single process using celery -A proj worker -B, but for production it’s recommended to run them separately for better fault isolation.
Dynamic Periodic Tasks
Sometimes you need to add or modify schedules at runtime. Celery Beat supports a Database Scheduler (django-celery-beat for Django, redbeat for Redis).
pip install django-celery-beat
Then create a PeriodicTask model entry via Django admin or programmatically:
from django_celery_beat.models import PeriodicTask, CrontabSchedule
schedule, _ = CrontabSchedule.objects.get_or_create(minute='0', hour='*/3')
PeriodicTask.objects.create(
crontab=schedule,
name='Run every 3 hours',
task='my_app.tasks.refresh_cache',
)
Monitoring & Management Tools
Visibility into a distributed task system is essential. Celery provides several first‑class tools.
Flower – Real‑Time Web UI
Flower is a lightweight web UI built on top of Celery events.
pip install flower
celery -A my_celery_app.celery_app flower --port=5555
Features:
- Task list with state (PENDING, STARTED, SUCCESS, FAILURE)
- Worker status (concurrency, memory usage)
- Real‑time charts of task rates
- Ability to revoke tasks, inspect queues, and view task arguments
Prometheus Exporter
If you already use Prometheus/Grafana, the celery-exporter or built‑in metrics in Celery 5+ can be scraped.
pip install prometheus-client
# In your Celery app
from prometheus_client import start_http_server, Counter
task_counter = Counter('celery_tasks_total', 'Total tasks executed', ['task_name', 'status'])
@app.task
def my_task():
try:
# task logic
task_counter.labels('my_task', 'success').inc()
except Exception:
task_counter.labels('my_task', 'failure').inc()
raise
Run the exporter alongside the worker:
celery -A my_celery_app.celery_app worker --loglevel=INFO &
python prometheus_exporter.py
Logging and Structured Logs
Celery integrates with Python’s logging module. You can configure a JSON formatter for centralized log aggregation (e.g., ELK, Loki).
# logging_config.py
LOGGING = {
'version': 1,
'formatters': {
'json': {
'()': 'pythonjsonlogger.jsonlogger.JsonFormatter',
'fmt': '%(asctime)s %(levelname)s %(name)s %(message)s %(process)d %(thread)d',
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'json',
},
},
'root': {
'handlers': ['console'],
'level': 'INFO',
},
}
Add app.conf.update(CELERYD_HIJACK_ROOT_LOGGER=False) to prevent Celery from overriding your config.
Scaling Celery Workers
Horizontal Scaling
Add more worker processes or machines. Celery automatically balances tasks across all available workers.
# On each host
celery -A my_celery_app.celery_app worker -Q high,default -c 4 --loglevel=INFO
-Qspecifies which queues to consume.-csets concurrency (default is number of CPU cores). Adjust based on I/O vs CPU bound tasks.
Autoscaling
The celery autoscale command can dynamically adjust concurrency based on workload.
celery -A my_celery_app.celery_app worker --autoscale=10,3
- Maximum 10 processes, minimum 3.
Dedicated Queues for Different Workloads
Separate queues allow you to allocate resources per workload type (e.g., email, video, analytics).
# tasks.py
@app.task(queue='email')
def send_email(...):
...
@app.task(queue='video')
def encode_video(...):
...
Run workers with queue affinity:
celery -A my_celery_app.celery_app worker -Q email -c 2
celery -A my_celery_app.celery_app worker -Q video -c 8
Resource Isolation with Containers
Running each worker in its own Docker container isolates dependencies and simplifies scaling via orchestration platforms (Docker Compose, Kubernetes, Swarm).
Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["celery", "-A", "my_celery_app.celery_app", "worker", "-Q", "default", "-c", "4", "--loglevel=INFO"]
docker-compose.yml
version: "3.8"
services:
redis:
image: redis:7-alpine
ports: ["6379:6379"]
worker:
build: .
environment:
- CELERY_BROKER_URL=redis://redis:6379/0
- CELERY_RESULT_BACKEND=redis://redis:6379/1
deploy:
replicas: 3
depends_on: [redis]
Kubernetes can further manage scaling via HorizontalPodAutoscaler based on CPU or custom metrics.
Best Practices & Common Pitfalls
1. Keep Tasks Idempotent
Because Celery guarantees at‑least‑once delivery, tasks may be executed multiple times (e.g., after a worker crash). Ensure side‑effects (database writes, external API calls) are idempotent or protected by deduplication keys.
def create_order(user_id, order_id):
if Order.objects.filter(id=order_id).exists():
return "Already processed"
# Proceed with creation
2. Use acks_late for Critical Tasks
Setting task_acks_late = True tells the worker to acknowledge the message after successful execution. If the worker crashes mid‑task, the broker will re‑queue it for another worker. This is essential for tasks that modify state.
app.conf.task_acks_late = True
3. Limit Task Payload Size
Large payloads increase serialization time and broker memory consumption. Prefer passing lightweight identifiers (primary keys, filenames) and fetching the full data inside the task.
4. Avoid Long‑Running Tasks in the Prefork Pool
The default prefork pool spawns separate processes; each long task blocks a worker slot. For CPU‑intensive work, consider using the threads pool (-P threads) or offloading to a specialized service (e.g., Spark for big data).
5. Monitor Queue Lengths
A growing queue indicates back‑pressure. Set alerts on queue depth via broker metrics (RabbitMQ management UI, Redis llen). Adjust concurrency or add workers before latency spikes.
6. Graceful Shutdown
When deploying new code, stop workers with TERM (or SIGINT) and wait for in‑flight tasks to finish. Celery’s --pool=solo mode helps with debugging, but in production use --pool=prefork and let Celery handle graceful termination.
# Stop a worker
celery -A proj control shutdown
7. Version Compatibility
Celery evolves rapidly; keep an eye on compatibility between Celery, Kombu, and the broker client libraries. Pin versions in requirements.txt to avoid surprising breakages.
celery==5.4.0
redis==5.0.1
amqp==5.2.0
8. Secure the Broker
Never expose your Redis or RabbitMQ instance without authentication. Use TLS for remote brokers, and set strong passwords. Celery supports URL‑encoded credentials:
broker_url = 'amqps://user:password@rabbitmq.example.com:5671/vhost'
Advanced Celery Patterns (Canvas, Groups, Chords)
Celery’s Canvas API lets you compose complex workflows out of simple tasks.
1. Chains
Execute tasks sequentially; output of one becomes input of the next.
from celery import chain
result = chain(
fetch_data.s(url),
process_data.s(),
store_result.s()
).apply_async()
2. Groups
Run a set of tasks in parallel and collect results.
from celery import group
ids = [1, 2, 3, 4]
result = group(download_image.s(i) for i in ids).apply_async()
# result.get() returns a list of each task's return value
3. Chords
A group followed by a callback that runs after all group members finish.
from celery import chord
header = group(resize_image.s(i) for i in image_ids)
callback = aggregate_results.s()
chord(header)(callback)
4. Map & Starmap
Convenient wrappers for parallel map operations.
from celery import group
# Equivalent to: map(square, [1,2,3,4])
result = group(square.s(i) for i in range(1,5)).apply_async()
5. Error Propagation in Canvas
You can attach error callbacks using link_error.
task_a.link_error(handle_failure.s())
6. Immutable Signatures
When you need to pass the same arguments regardless of previous task results, use .si() (immutable signature).
chain(task_a.si(), task_b.s()) # task_b receives task_a's result
chain(task_a.si(), task_c.si()) # task_c receives the original args
Deploying Celery in Production (Docker & Kubernetes)
Docker Compose for Development
version: "3.9"
services:
redis:
image: redis:7-alpine
ports: ["6379:6379"]
worker:
build: .
command: celery -A my_celery_app.celery_app worker -Q default -c 4 --loglevel=INFO
environment:
- CELERY_BROKER_URL=redis://redis:6379/0
- CELERY_RESULT_BACKEND=redis://redis:6379/1
depends_on: [redis]
beat:
build: .
command: celery -A my_celery_app.celery_app beat --loglevel=INFO
environment:
- CELERY_BROKER_URL=redis://redis:6379/0
depends_on: [redis]
flower:
image: mher/flower
ports: ["5555:5555"]
command: flower --broker=redis://redis:6379/0
depends_on: [redis]
Run with docker-compose up -d. This setup gives you a broker, two workers, Beat, and a monitoring UI.
Kubernetes Manifest Overview
apiVersion: apps/v1
kind: Deployment
metadata:
name: celery-worker
spec:
replicas: 4
selector:
matchLabels:
app: celery-worker
template:
metadata:
labels:
app: celery-worker
spec:
containers:
- name: worker
image: myregistry/my_celery_app:latest
args: ["celery", "-A", "my_celery_app.celery_app", "worker", "-Q", "default", "-c", "4", "--loglevel=INFO"]
env:
- name: CELERY_BROKER_URL
value: "redis://redis:6379/0"
- name: CELERY_RESULT_BACKEND
value: "redis://redis:6379/1"
resources:
limits:
cpu: "500m"
memory: "256Mi"
---
apiVersion: v1
kind: Service
metadata:
name: redis
spec:
ports:
- port: 6379
selector:
app: redis
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
Key considerations:
- Horizontal Pod Autoscaler (HPA) – Scale workers based on CPU or custom queue length metrics.
- Pod Disruption Budgets – Ensure a minimum number of workers stay up during node drains.
- Readiness/Liveness Probes – Use Celery’s
pingcommand (celery -A proj inspect ping) as a liveness check.
Security Considerations
- Broker Authentication & TLS – Configure passwords and enable SSL/TLS for RabbitMQ (
amqps://) or Redis (rediss://). - Result Backend Encryption – When using a DB backend, enforce TLS connections and restrict access via firewall rules.
- Task Serialization – Prefer
jsonormsgpack. Avoidpickle(default in older versions) as it can execute arbitrary code. - Signed Tasks – Celery supports task signing to verify that a task originated from a trusted producer.
# celery_config.py
task_serializer = 'json'
accept_content = ['json']
task_send_sent_event = True
task_send_sent_event = True
result_serializer = 'json'
- Least Privilege Workers – Run Celery workers under a non‑root user, limit OS capabilities, and mount only required files.
- Network Segmentation – Place the broker in a private subnet; expose only the necessary ports to the application tier.
Conclusion
Celery remains a powerful, battle‑tested solution for asynchronous processing in Python ecosystems. By mastering its core concepts—brokers, workers, task definition, retries, periodic scheduling, monitoring, and scaling—you can unlock massive performance gains and build resilient architectures that keep user‑facing services snappy while handling heavyweight workloads behind the scenes.
Key takeaways:
- Start simple. A single worker and Redis broker are enough for development.
- Plan for failure. Use
acks_late, idempotent tasks, and proper retry policies. - Observe constantly. Flower, Prometheus, and broker metrics give you the insight needed to act before bottlenecks appear.
- Scale intentionally. Separate queues, autoscaling, and containerization let you grow without chaos.
- Secure everything. Authentication, TLS, and safe serialization protect your data pipelines.
Whether you’re building a modest Django site that sends emails or orchestrating a massive micro‑service ecosystem with machine‑learning pipelines, Celery provides the flexibility and reliability to meet those demands. Embrace the patterns, monitor rigorously, and let Celery handle the heavy lifting so you can focus on delivering value.
Resources
Celery Official Documentation – Comprehensive guide, API reference, and best‑practice sections.
https://docs.celeryproject.org/en/stable/RabbitMQ – Getting Started – Detailed guide to installing, configuring, and securing RabbitMQ for production.
https://www.rabbitmq.com/getstarted.htmlFlower – Real‑Time Monitoring for Celery – Installation, configuration, and usage examples.
https://flower.readthedocs.io/en/latest/Django‑Celery‑Beat – Database‑backed periodic task scheduler for Django projects.
https://github.com/celery/django-celery-beatPrometheus Python Client – Export custom Celery metrics to a Prometheus server.
https://github.com/prometheus/client_pythonCelery Best Practices (2024) – Blog post covering production patterns, security, and scaling.
https://blog.celeryproject.org/2024/09/15/production-best-practices/