Skip to content

Running Validibot with Docker

Validibot runs entirely in Docker containers for local development — Django, a Celery worker, a scheduler, Postgres, Redis, and a local mail catcher. You don't need Python, Postgres, or Node installed on your machine; Docker handles everything.

This page gets you from a fresh machine to a running app in about ten minutes.

Before you start

You need three things installed:

Tool What it's for Get it
Docker Desktop (Mac/Windows) or Docker Engine / Podman (Linux) Runs all the containers. Make sure it's actually started before you continue. https://docs.docker.com/get-docker/
just The command runner. Every shortcut below (just local up) comes from it. https://just.systems/
git To clone the repository. https://git-scm.com/downloads

You'll also want 4 GB of RAM free for Docker (8 GB recommended) and these ports available: 8000 (web), 8025 (mail). just local up checks these two for you and stops with a friendly message if something else is using them.

What is just? It's a small command runner (think "Make, but friendlier"). Validibot's commands live in a justfile at the repo root, so you run short, memorable commands like just local up instead of long docker compose invocations. Install it once (brew install just, or see https://just.systems/); the How the just commands work section below explains what they do under the hood.

Step 1 — Get the code

git clone https://github.com/danielmcquillen/validibot.git
cd validibot

Run every command below from inside this validibot folder.

Step 2 — Create your env files

These hold your local settings and secrets. Copy them from the examples:

mkdir -p .envs/.local
cp .envs.example/.local/.django   .envs/.local/.django
cp .envs.example/.local/.postgres .envs/.local/.postgres

⚠️ The .envs/ folder holds real secrets and is gitignored. Never commit it. See Environment Configuration for details. (Pro/Enterprise users also copy .build — see Going further.)

Step 3 — Set the three required values

Open .envs/.local/.django and replace the three !!!SET...!!! placeholders. The app will not start in local development without all three — the local settings raise an error if the secret key or MFA key is missing.

Variable What it is Generate it with
DJANGO_SECRET_KEY Django signing key python -c "import secrets; print(secrets.token_urlsafe(50))"
DJANGO_MFA_ENCRYPTION_KEY Fernet key that encrypts MFA secrets. Must be a valid Fernet key, not just any random string. python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
SUPERUSER_PASSWORD Your admin login password Pick a strong one — this is how you'll sign in

No local Python with cryptography? Generate the Fernet key with Docker, which you already have:

docker run --rm python:3.13-slim sh -c "pip install -q cryptography && \
  python -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())'"

.envs/.local/.postgres needs no edits — its defaults (validibot / validibot) work as-is for local development.

Step 4 — Build and start

just local up

…which runs:

docker compose -f docker-compose.local.yml up -d

The first run builds the images and downloads base layers — expect a few minutes. Later starts are fast. (Need to rebuild after changing dependencies or a Dockerfile? Use just local build.)

On first start the web container automatically applies database migrations and runs setup_validibot, which configures site settings, roles, the built-in validators, and creates your admin user from the SUPERUSER_* values. You don't need to run anything else.

Step 5 — Open the app

Go to http://localhost:8000 and sign in:

  • Username: admin (the SUPERUSER_USERNAME default)
  • Password: the SUPERUSER_PASSWORD you set in step 3

That's it — you're running Validibot. 🎉

Did it work?

Quick checks once the stack is up:

just local ps                          # all services should be "Up"
curl http://localhost:8000/health/     # should return OK
just local manage "check_validibot"    # confirms setup is correct
  • App: http://localhost:8000
  • Captured emails: http://localhost:8025 (Mailpit — every email the app "sends" locally lands here instead of a real inbox)

How the just commands work

You don't need this to get running, but it helps to know what just is doing.

The repo root has a justfile that acts as an orchestrator. It pulls in shared helpers (import 'just/common.just') and wires up each command group as a module — mod local 'just/local' is what gives you just local <command>. There are sibling groups for the other workflows: just local-pro, just local-cloud, just self-hosted, just gcp. Run plain just to see the menu, or just --list for everything.

For local development, the recipes in just/local/mod.just are thin wrappers around Docker Compose, always run from the repo root so relative paths resolve:

Command What it actually runs
just local up Pre-checks ports 8000/8025, then docker compose -f docker-compose.local.yml up -d (builds automatically on first run).
just local build docker compose … up -d --build — rebuild after dependency or Dockerfile changes.
just local rebuild Same, but also wipes and repopulates the venv volume (use after dependency upgrades).
just local down Stop containers; data is kept.
just local logs Follow logs from all services.
just local ps Container status.
just local migrate Run manage.py migrate in the web container.
just local manage "<cmd>" Run any manage.py command (e.g. createsuperuser).
just local clean Stop and delete all data (volumes removed).

One nicety: if .envs/.local/.build exists, the recipes automatically pass it as --env-file; if it doesn't, they skip it. So community users never have to think about that file. Full reference: Justfile Guide.

What's running

Service What it does
web The Django app (dev server on 8000, your code hot-reloads).
worker Celery worker for background jobs; also launches advanced validator containers via the Docker socket.
scheduler Celery Beat — periodic jobs like cleanup and data expiry.
postgres The database.
redis Task queue for Celery.
mailpit Captures local email at http://localhost:8025.

Entrypoint and start scripts live in compose/local/django/ and wait for Postgres before launching.

Troubleshooting first-run issues

"Cannot connect to the Docker daemon" — Docker Desktop isn't running. Start it, wait for it to settle, then retry.

"Port 8000 / 8025 is in use"just local up detected a leftover process and stopped. Kill the process it names, then re-run just local up.

The web container exits right after starting — almost always a missing or invalid value from step 3. Check just local logs web; a missing DJANGO_SECRET_KEY or DJANGO_MFA_ENCRYPTION_KEY (or an MFA key that isn't a valid Fernet key) raises a clear error at startup.

First build is very slow — normal the first time; it's cached afterward. Watch progress with just local logs.

The page loads but looks unstyled — prebuilt CSS/JS ships in the repo, so this is rare. You only need Node if you're editing SCSS/TypeScript: run npm install && npm run build on the host to regenerate the assets.

Start completely cleanjust local clean removes containers and data, then just local up rebuilds from scratch. See also Reset an Environment.

Advanced validators (optional — skip for your first run)

You don't need these to get started. The built-in validators (JSON Schema, XML Schema, Tabular, etc.) run inside the Django process and work the moment the stack is up.

Heavyweight validators — EnergyPlus, FMU, and SHACL — run as separate sibling containers that the worker launches on demand. The matching image must already exist on your Docker host. They live in a separate repo and build with one command — no registry, login, or push needed for local use:

git clone https://github.com/danielmcquillen/validibot-validator-backends.git
cd validibot-validator-backends
just build-all          # or build one: just build energyplus

This produces images named validibot-validator-backend-<slug>:latest (slugs: energyplus, fmu, shacl). The worker finds each one by that name automatically — there's nothing to configure. By default these containers run with no network access for safety (they exchange files through a shared storage volume); uncomment VALIDATOR_NETWORK in the compose file only if a validator genuinely needs the internet.

⚠️ Only run validator backend images you build and control yourself — they execute with access to your validation data.

For registry-based deployment (build-and-push to GCP Artifact Registry, etc.) and per-backend details, see the validibot-validator-backends README and Execution Backends.

Going further

  • Pro / Enterprise, the MCP server, signed credentials: these are opt-in and documented in Run Validibot Locally (copy .envs.example/.local/.build, set VALIDIBOT_COMMERCIAL_PACKAGE, and use just local-pro up).
  • Production-style stack (Gunicorn, no code mount, for parity testing): docker-compose.production.yml via just self-hosted bootstrap. See Deploy with Docker Compose.
  • VS Code test runner: the repo ships .vscode/.env pointing pytest at the Docker Postgres. If the Testing panel hangs, confirm the interpreter is .venv/bin/python and that Postgres is up (just local up).

Note on local-cloud: if you see just local-cloud ... recipes, those drive the separate hosted-Cloud workflow, not the self-hosted path — you can ignore them.

Where things live

  • compose/local/django/Dockerfile: base image for local dev (includes dev extras).
  • compose/production/django/Dockerfile: base image for production (no dev extras).
  • docker-compose.local.yml: local dev (runserver, code mounted) with web + worker + scheduler + postgres + redis + mailpit.
  • docker-compose.production.yml: production-like (gunicorn, no code mount).
  • just/local/mod.just: the local development recipes (up, down, build, logs, …).
  • compose/local/django/entrypoint.sh and start.sh: wait for DB, fix Docker socket permissions, run migrations, first-run setup, start dev server on 8000.
  • compose/production/django/entrypoint.sh and start.sh: wait for DB, fix Docker socket permissions if mounted, collectstatic, skip setup until migrations exist, then start Gunicorn on 8000.