dlboard

dlboard official documentation

View on GitHub

Docker

What is this

The root Dockerfile builds the dlboard distribution into a container image. It’s a two-stage build — a uv-based builder stage that resolves and compiles the project, and a slim runtime stage that only ever sees the finished virtual environment, never uv, a compiler, or the source tree outside what got installed into site-packages.

The image’s default deployment is Postgres metadata + local-disk artifacts, with password sign-in — composed in dlboard/scripts/docker_deployment.py:

PLUGINS = [*POSTGRES_STORAGE, *PASSWORD_AUTH, *BUILTIN_BACKEND, *BUILTIN_CHARTS, themes.default]

This is the simplest deployment that’s still a real, multi-person deployment rather than dlboard serve local’s single-user setup: one Postgres database plus one volume for artifact blobs, nothing else to run or pay for. The image also ships the s3 extra, so pointing it at an S3-protocol store instead needs no rebuild — see “Using an S3 artifact store instead” below.

When you’d need this

You’re running dlboard in Docker/Compose/Kubernetes/ECS rather than installing it directly with pip/uv tool install. If you’re running it as a plain process on a machine you control, see the root README instead — there’s no reason to containerize just to get dlboard serve local or serve custom running.

Running it

It needs a reachable Postgres to actually start — that’s not optional, since the default deployment has no other metadata store to fall back to. Artifact storage defaults to a local directory inside the container (/data/artifacts), so it only needs a volume, not another service.

To try it with no infrastructure of your own, docker-compose.yml at the repo root is entirely self-contained apart from one thing it refuses to make up a default for — a session secret:

export DLBOARD_SECRET_KEY=$(openssl rand -hex 32)
docker compose up --build
docker compose exec dlboard \
  dlboard users set-password alice --admin --plugins dlboard.scripts.docker_deployment:PLUGINS

It builds the image and brings up a Postgres container alongside it — nothing else to configure first. Both of its volumes (dlboard-postgres, dlboard-artifacts) genuinely persist across docker compose down/up (no -v); that was checked by actually restarting the stack and confirming a project and an uploaded artifact were both still there afterward, not assumed from “it’s a volume.” It’s still a local trial, not a deployment you’d run as-is: docker compose down -v throws both volumes away on purpose.

Pointed at your own Postgres instead, override that same file’s environment (or write your own docker-compose.yml/Kubernetes manifest): point DLBOARD_POSTGRES_HOST at your real database, set a real DLBOARD_SECRET_KEY (openssl rand -hex 32), and either keep mounting a volume at /data for artifacts or switch to S3 (below).

Configuration

Every setting is an environment variable — nothing is passed on the container’s command line. These are the ones every deployment needs; the full lists (TLS, pooling, timeouts, …) are in storage.md (PostgresSettings) and auth.md (PASSWORD_AUTH’s settings table).

Variable Required  
DLBOARD_POSTGRES_HOST yes Also _PORT (5432), _DATABASE (dlboard), _USER (dlboard), _PASSWORD.
DLBOARD_ARTIFACT_STORE_LOCATION no Where artifact blobs land inside the container. Defaults to /data/artifacts — mount a volume at /data to persist them.
DLBOARD_SECRET_KEY yes Signs session cookies for the password auth plugin this image runs. A missing one is a startup error, not a silent fall-through to anonymous access — generate one with openssl rand -hex 32.
DLBOARD_ADMIN_USERS no Comma-separated usernames made admin whenever they sign in — the alternative to creating the first admin by hand (below).
DLBOARD_MAX_UPLOAD_MB no Hard cap on any single request body, mainly artifact uploads. Defaults to 256.
DLBOARD_TELEMETRY_METRICS no prometheus to serve /metrics, otlp to push to a collector, or both – the image has the otel extra. See telemetry.md.

Serve it over HTTPS in front of a reverse proxy — session cookies are Secure by default, which most HTTP clients (browsers included, localhost sometimes excepted) silently refuse to send back over a plain-HTTP connection, breaking sign-in rather than just weakening it. docker-compose.yml sets DLBOARD_SECURE_COOKIES=false for exactly this reason, since it’s plain HTTP by design — leave it at its default (true) everywhere else (see auth.md for rate-limiting /login too).

Creating the first admin

Without a shell (a NAS or hosting UI that only shows you logs):

  1. Start the container with DLBOARD_PASSWORD_FIRST_ADMIN_SETUP=true.
  2. Open /setup on your server, for example https://dlboard.example.com/setup. Opening it writes a setup token to the container log (First-admin setup is open. Setup token: ...).
  3. Enter that token along with a username and password. You’re signed in as an admin.
  4. Remove DLBOARD_PASSWORD_FIRST_ADMIN_SETUP (or set it to false). The container won’t start again with it still on, since the database now has users.

/setup is off unless you set that variable, and even then it only works while the database has no users at all. Once anyone exists it returns 404 for good, even if every admin is later demoted. Create the admin before you share the address: anyone who can read the container log in that window could do it first. If DLBOARD_PASSWORD_SIGNUP lets people sign up, the first signup also closes /setup, and you’d use the command below instead.

From a shell, you can also create the admin directly:

docker run --rm -it --env-file .env dlboard \
  users set-password alice --admin --plugins dlboard.scripts.docker_deployment:PLUGINS

--env-file .env needs the same DLBOARD_POSTGRES_* variables as the running container, since this writes to the same database. After that, admins can add more users and reset passwords from Admin → Users in the app itself — see auth.md.

Authenticating a training script

Password sign-in means dlboard serve local’s “no key needed” shortcut doesn’t apply here — a training script is a separate caller from your browser session, and it authenticates the way client.md describes for any signed-in deployment:

  1. Sign in at http://localhost:8050 (or wherever you exposed it) with the user you created above.
  2. Create an API token on that user’s Account page.
  3. Wherever DLBoardLogger runs, set DLBOARD_API_KEY to that token and point it at the server:

    from dlboard.client.dlboard_logger import DLBoardLogger
    
    logger = DLBoardLogger(project_id=project.id, server_url="http://localhost:8050")
    

If the training script runs in its own container on the same Compose network, use the service name instead of localhost — server_url="http://dlboard:8050". The logger checks the key against /api/v1/whoami before training starts, so a missing or wrong DLBOARD_API_KEY fails immediately rather than silently dropping every metric.

Using an S3 artifact store instead

Local disk is the simplest default, not the only option — the image is already built with the s3 extra, so swapping dlboard.plugins.POSTGRES_STORAGE for POSTGRES_S3_STORAGE (postgres + S3 + the artifact purge worker) needs no rebuild, just your own small module instead of docker_deployment.py (dlboard deliberately doesn’t ship a ready-made S3 “deployment” of its own — see storage.md):

# mydeployment.py
from dlboard.plugins import BUILTIN_BACKEND, BUILTIN_CHARTS, PASSWORD_AUTH, POSTGRES_S3_STORAGE, themes

PLUGINS = [*POSTGRES_S3_STORAGE, *PASSWORD_AUTH, *BUILTIN_BACKEND, *BUILTIN_CHARTS, themes.default]

Mount it into the container and point --plugins at it instead:

docker run -v ./mydeployment.py:/app/mydeployment.py:ro -e PYTHONPATH=/app \
  -e DLBOARD_POSTGRES_HOST=... -e DLBOARD_S3_BUCKET=... -e DLBOARD_SECRET_KEY=... \
  dlboard serve custom --plugins mydeployment:PLUGINS --host 0.0.0.0

DLBOARD_S3_BUCKET is required once you do this; DLBOARD_S3_ENDPOINT_URL/DLBOARD_S3_ADDRESSING_STYLE=path instead of AWS for a self-hosted S3-protocol store (MinIO, VAST, …). See storage.md for the full S3Settings list.

The docker-compose.yml at the repo root deliberately doesn’t do this: a throwaway S3-protocol emulator worth bundling for a zero-setup local trial turned out not to exist for free — LocalStack’s free tier doesn’t actually persist state across a restart (checked, not assumed) and MinIO’s own images now require a Docker Hub login (see dlboard/conftest.py’s s3_test_server). Local disk sidesteps both and is just as real a storage choice for a small deployment.

Running a different plugin list

More generally, the image’s ENTRYPOINT/CMD just runs dlboard serve custom --plugins dlboard.scripts.docker_deployment:PLUGINS --host 0.0.0.0 — override either to run your own PLUGINS module (a different auth provider, a different theme, S3 as above) the same way:

FROM dlboard
COPY mydeployment.py /app/mydeployment.py
ENV PYTHONPATH=/app
CMD ["serve", "custom", "--plugins", "mydeployment:PLUGINS", "--host", "0.0.0.0"]

See plugins/overview.md for what a PLUGINS module needs, and storage.md/auth.md for the built-in plugins it can compose.

CI and releases

The image is published to ghcr.io/dlangerm/dlboard (amd64 and arm64) under tags that trade stability for freshness:

Tag Moves when Use it for
X.Y.Z, X.Y a vX.Y.Z tag is released (X.Y follows the newest patch) pinning an exact release
stable a release is published production: only ever a release
beta, latest a change lands on main and passes CI trying bugfixes before they are released
sha-<commit> never; one per landing on main rolling back a beta deployment
docker pull ghcr.io/dlangerm/dlboard:stable

Because beta and latest track unreleased code, a database migration they carry has not had a release’s worth of use; see compatibility.md before pointing one at data you care about.

.github/workflows/ci.yml pushes latest/beta/sha-* once its test job passes on main (a PR only gets the build-only docker-build check). Pushing a vX.Y.Z tag runs .github/workflows/release.yml, which publishes dlboard-client/dlboard to PyPI and then the release tags above.

See CHANGELOG.md for what each release contains, and the repo’s GitHub Releases page for the full tag history.