Skip to content

Deploy with Docker Compose

This is the main self-hosted production target for Validibot. Use it when you want to run Validibot on a VPS, a single cloud VM, an on-prem server, or any host you control with Docker.

For most self-hosted customers, this is the best production path.

When to choose this target

Choose Docker Compose if you want:

  • A production-style deployment on infrastructure you control
  • A simpler alternative to Kubernetes
  • A good fit for DigitalOcean, Hetzner, EC2, or on-prem servers
  • A deployment that can stay online for real users behind a reverse proxy

Choose Run Validibot Locally instead if you only want to evaluate the product on your laptop.

What this target runs

The Docker Compose production stack uses docker-compose.production.yml and the just self-hosted ... commands.

It runs:

  • web with Gunicorn
  • worker for background jobs and validator execution
  • scheduler for periodic tasks
  • postgres
  • redis

Two optional services live behind Compose profiles and stay off unless you opt in:

  • caddy — a bundled reverse proxy that auto-issues a Let's Encrypt certificate. Enable by setting COMPOSE_PROFILES=caddy before just self-hosted deploy. Off by default because most operators already run nginx, Traefik, Cloudflare Tunnel, or a hosting-provider load balancer. See Reverse Proxy Setup for the full set of options.
  • mcp — the standalone FastMCP server that exposes validation workflows to AI agents. Pro feature, opt-in via ENABLE_MCP_SERVER=true (covered below).

First-time install

  1. Create the production env directory:
    mkdir -p .envs/.production/.self-hosted
  1. Copy the env templates:
cp .envs.example/.production/.self-hosted/.django .envs/.production/.self-hosted/.django
cp .envs.example/.production/.self-hosted/.postgres .envs/.production/.self-hosted/.postgres

Also copy the .build file — it holds both commercial-package installation vars (Pro / Enterprise) and recipe-level knobs like ENABLE_MCP_SERVER. Safe to copy for any deployment; all vars have sensible defaults when left empty.

cp .envs.example/.production/.self-hosted/.build .envs/.production/.self-hosted/.build
  1. Edit both files and replace the placeholder values.

Make sure you set:

   - `DJANGO_SECRET_KEY`
   - `DJANGO_ALLOWED_HOSTS`
   - `SITE_URL`
   - `DJANGO_MFA_ENCRYPTION_KEY`
   - `WORKER_API_KEY`
   - `POSTGRES_PASSWORD`
   - `SUPERUSER_PASSWORD`

If you are installing a commercial package, edit .envs/.production/.self-hosted/.build too:

VALIDIBOT_COMMERCIAL_PACKAGE=validibot-pro==<version>
VALIDIBOT_PRIVATE_INDEX_URL=https://<license-credentials>@pypi.validibot.com/simple/

Use validibot-enterprise==<version> instead if you purchased Enterprise. You can also use a quoted exact wheel URL on pypi.validibot.com that includes #sha256=<hash> instead of a package name and version.

!!! warning "Credentials in build args are visible in image metadata" These values are passed to docker build as build args and end up in the final image's docker history. If you push the built image to a private registry only you control, that is usually acceptable. If you push to a shared registry, or you export the image via docker save and share the archive, the embedded PyPI credentials are recoverable. Rotate the index credentials regularly, and do not share built images outside your own trust boundary.

   A BuildKit-secrets-based alternative that avoids this exposure is
   on the roadmap — see the "Build security" issue in the private
   project tracker for the migration plan.

Then point Django at the Pro-activating settings module by setting DJANGO_SETTINGS_MODULE in your .envs/.production/.self-hosted/.django:

DJANGO_SETTINGS_MODULE=config.settings.production_pro

That settings module adds validibot_pro to INSTALLED_APPS, which is what Django needs in order to import the package and run its license-registration hook. Do not edit config/settings/base.py directly — that makes future upgrades harder; the dedicated settings module is the supported path.

To also include the MCP server (exposes validation workflows to AI agents over the Model Context Protocol), copy the MCP env file and flip the build flag:

cp .envs.example/.production/.self-hosted/.mcp \
   .envs/.production/.self-hosted/.mcp

Then edit .envs/.production/.self-hosted/.mcp and set VALIDIBOT_MCP_SERVICE_KEY to a long random string. The same value must be set as VALIDIBOT_MCP_SERVICE_KEY in your .django file — that shared secret is how the MCP server authenticates itself to Django's helper API. Generate one with:

python -c "import secrets; print(secrets.token_urlsafe(32))"

Finally, in .envs/.production/.self-hosted/.build:

ENABLE_MCP_SERVER=true

The just self-hosted up / build recipes source the .build file at the top and activate the mcp Compose profile when the flag is truthy.

License gate. The MCP code itself lives in this repo at mcp/ and is free to build, but at startup the server calls GET /api/v1/license/features/ against the Django API and refuses to serve traffic unless mcp_server is advertised — which only happens when validibot-pro (or enterprise) is installed via VALIDIBOT_COMMERCIAL_PACKAGE. So a community-only deployment that flips ENABLE_MCP_SERVER=true will build and start the container, then watch it exit on the license check. If you're running Pro, you're all set.

  1. Validate the env files and bootstrap the deployment:
just self-hosted check-env
just self-hosted bootstrap

bootstrap is the recommended first-run command. It:

  • validates the env files (check-env)
  • builds and starts the stack
  • waits for the web container to come up
  • applies migrations
  • runs setup_validibot to seed roles and the superuser
  • runs ensure_oidc_clients to register the OIDC clients (needed if you enable MCP)
  • runs check_validibot as a final sanity check

Enable signed credentials on Docker Compose

If you purchased Pro or Enterprise and want signed credentials, the simplest self-hosted option is the local file signing backend.

Create a private signing key on the host:

mkdir -p .envs/.production/.self-hosted/keys
openssl ecparam -name prime256v1 -genkey -noout \
  -out .envs/.production/.self-hosted/keys/credential-signing.pem
chmod 600 .envs/.production/.self-hosted/keys/credential-signing.pem

Then add this to .envs/.production/.self-hosted/.django:

SIGNING_KEY_PATH=/run/validibot-keys/credential-signing.pem
CREDENTIAL_ISSUER_URL=https://validibot.example.com

The production compose file mounts .envs/.production/.self-hosted/keys into the web and worker containers at /run/validibot-keys.

If you rotate the key later, existing credentials remain valid only as long as their verifying public key is still exposed through the instance JWKS. Plan key rotation deliberately.

Verify the deployment

After bootstrap completes:

just self-hosted status
just self-hosted health-check
just self-hosted doctor                # full doctor diagnostic

At this point the app is running on port 8000 on the host. For a real deployment, put it behind a reverse proxy before exposing it publicly.

Reverse proxy and TLS

Validibot does not ship with an always-on proxy container by default. That keeps the stack compatible with self-hosters who already have Caddy, Traefik, nginx, or Cloudflare Tunnel in place.

Use one of these guides next:

Updates and day-two operations

Routine operations use the same just self-hosted ... namespace:

just self-hosted deploy
just self-hosted upgrade --to v0.9.0
just self-hosted logs
just self-hosted backup
just self-hosted list-backups
just self-hosted restore backups/<id>

deploy is for starting or rebuilding the currently checked-out stack. upgrade --to <version> is the safer day-two path because it takes a manifested backup and runs pre-flight and post-flight checks around the migration.

Security and isolation notes

There are a few important production details to understand:

  • The worker is the only service that gets Docker socket access for advanced validator execution.
  • The reverse proxy should terminate TLS and keep internal services private.
  • Secrets belong in .envs/, never in the repo.
  • Advanced validator images should be images you built and control yourself.

For the operator responsibilities and safe-default expectations, read Docker Compose Deployment Responsibility.

Good fits for this target

Docker Compose is a good fit when:

  • you want to self-host on one machine
  • you are comfortable managing OS updates and backups
  • you do not need GCP-specific infrastructure

It is also the easiest target to run on AWS today, because the AWS-specific deployment automation is not implemented yet.