Django
Python
Application Startup
Code Execution
Web Development

Execute code when Django starts ONCE only?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Django gives you startup hooks, but "run once when Django starts" is trickier than it sounds. Development auto-reload, multiple worker processes, management commands, and container restarts can all cause initialization code to run more than once, so the real solution is usually a combination of the right hook and idempotent logic.

Use AppConfig.ready for app initialization, not one-time global jobs

The standard Django hook is AppConfig.ready. It runs after Django loads installed apps, which makes it a good place to register signals or wire lightweight initialization.

python
1from django.apps import AppConfig
2
3class BillingConfig(AppConfig):
4    default_auto_field = "django.db.models.BigAutoField"
5    name = "billing"
6
7    def ready(self):
8        import billing.signals  # noqa: F401

This is the correct place for setup that is safe to run more than once in a process. It is not a guarantee of exactly one execution across the whole deployment.

Why ready is not truly "once"

During development, Django's auto-reloader can import the app twice. In production, a process manager such as Gunicorn or uWSGI can start multiple worker processes, and each process may execute the startup hook. Management commands can also load Django and trigger the same code path.

That means code with side effects such as sending emails, seeding data blindly, or starting background threads is risky inside ready unless you protect it carefully.

Make startup logic idempotent

If the work must happen at application boot time, design it so repeated execution is harmless. Database-backed guards are often the simplest solution.

python
1from django.db import transaction
2from django.db.models import Exists, OuterRef
3from myapp.models import StartupMarker
4
5
6def run_once_per_database(key: str, callback):
7    with transaction.atomic():
8        exists = StartupMarker.objects.select_for_update().filter(name=key).exists()
9        if exists:
10            return False
11        StartupMarker.objects.create(name=key)
12
13    callback()
14    return True

This pattern is useful when the job is tied to shared state and must not run multiple times across workers.

Prefer management commands or migrations for one-off setup

If the code is truly a one-time task, it often should not run at server startup at all. Use a data migration, deployment step, or management command instead.

python
1from django.core.management.base import BaseCommand
2from myapp.services import warm_cache
3
4class Command(BaseCommand):
5    help = "Warm important cache entries"
6
7    def handle(self, *args, **options):
8        warm_cache()
9        self.stdout.write(self.style.SUCCESS("Cache warmup complete"))

This is easier to reason about than hidden startup behavior. Operators can run it intentionally during deployment, and logs show when it happened.

Use process startup only for process-local concerns

Some tasks really do belong at startup, but only when they are local to a process. Examples include signal registration, connecting metrics hooks, or initializing in-memory caches that are safe per worker.

python
1_started = False
2
3
4def init_local_cache():
5    global _started
6    if _started:
7        return
8    _started = True
9    print("local cache initialized")

This process-local guard prevents duplicate initialization within one process, but it does not coordinate across processes or machines.

Avoid starting unmanaged background threads inside Django startup

A frequent anti-pattern is starting a scheduler, consumer, or infinite loop from ready. That tends to break under reloads, scale-out deployments, and process restarts. If you need recurring work, use Celery beat, a cron job, or your platform scheduler. If you need long-running consumers, run them as separate processes.

Django startup hooks are for application wiring, not for embedding your whole job system into the web process.

Common Pitfalls

  • Treating AppConfig.ready as a hard guarantee that code runs exactly once in the entire deployment.
  • Performing destructive or non-idempotent side effects during startup.
  • Starting background threads or schedulers inside Django web workers.
  • Forgetting that management commands also load Django and may trigger startup hooks.
  • Hiding deployment tasks in startup code instead of using migrations or explicit commands.

Summary

  • Use AppConfig.ready for lightweight app initialization.
  • Assume startup hooks may run multiple times across reloads and worker processes.
  • Make any startup side effect idempotent if repetition is possible.
  • Prefer migrations or management commands for true one-time tasks.
  • Keep long-running jobs outside the Django web process.

Course illustration
Course illustration

All Rights Reserved.