Build from Source
TestPlanIt's prebuilt Docker images are built for the hosted service — they
bake BASE_DOMAIN=testplanit.com into the image, so they only work on that
domain. To run TestPlanIt on your own domain — and to try beta /
pre-release builds before an official release — you build the image yourself
from source. The compose files build from the Dockerfile, so this is a
one-command process.
Trying a beta (pre-release)
Betas of upcoming releases are published as GitHub pre-releases and distributed as source only (no image). To try one:
- Open the Releases page,
find the latest entry marked Pre-release (e.g.
v1.0.0-beta.1), and download its Source code archive — or check out the tag:git clone https://github.com/TestPlanIt/testplanit.gitcd testplanitgit checkout v1.0.0-beta.1 - Follow Build and run below.
⚠️ Pre-releases are not production-ready. Always back up your database first (see Upgrading an existing database).
Prerequisites
- Docker and the Docker Compose plugin
- A clone (or source archive) of the version you want to build
Configure your environment
Copy the example env file and fill in your values:
cp testplanit/.env.example testplanit/.env.production
At minimum set DATABASE_URL, NEXTAUTH_SECRET, NEXTAUTH_URL, and
VALKEY_URL. See Installation and
Deployment for the full configuration reference.
Build and run
The production compose file builds the image from the Dockerfile and starts
the full stack (app, workers, Postgres, Valkey, Elasticsearch, MinIO):
docker compose -f testplanit/docker-compose.prod.yml up -d --build
Images and custom domains
How TestPlanIt serves uploaded images (project icons, avatars, attachments) is
fixed at build time by the SELF_HOSTED build argument. This is the single
most common self-hosting gotcha, so read this before your first build.
⚠️
SELF_HOSTED(andBASE_DOMAINbelow) are build-time arguments. They are baked into the Next.js standalone build (.next/required-server-files.json) and the running server reads that frozen config. Setting them only at runtime in.env.productionis too late and silently has no effect — the symptom is broken images that return400from/_next/image. They must reachnext build, i.e. theprodservice'sbuild.args(already wired in the compose file). Compose fills those from your shell / default.env/--env-file— not from the.env.productionenv_file, which only injects runtime variables into the container.
Default — SELF_HOSTED=true (recommended for self-hosting). The compose file
defaults to this. Next.js's image optimizer is turned off, so <Image>
renders a plain <img> pointing straight at your storage. One build then works
on any domain with no allowlist to configure, and BASE_DOMAIN is ignored.
The default build needs nothing extra:
docker compose -f testplanit/docker-compose.prod.yml up -d --build
Optional — SELF_HOSTED=false (Next.js image optimizer on). Turn the
optimizer back on only if you want automatic image resizing/re-encoding. You must
then set BASE_DOMAIN to your real domain: it is baked into next.config's image
remotePatterns allowlist so the optimizer accepts *.your-domain storage URLs.
Requests for any host outside the allowlist (including the example.com default)
return 400.
SELF_HOSTED=false BASE_DOMAIN=example.com \
docker compose -f testplanit/docker-compose.prod.yml up -d --build
Both are wired through the prod service's build.args in the compose file
(SELF_HOSTED: ${SELF_HOSTED:-true} and BASE_DOMAIN: ${BASE_DOMAIN:-example.com}).
Upgrading an existing database
As of v1.0 TestPlanIt applies schema changes with versioned migrations
(zenstack migrate deploy), which the container runs automatically on startup.
A database created by an older TestPlanIt release has no migration history,
so the startup migrate deploy would try to re-create the baseline schema and
fail. Back up your database, then run the one-time baseline before the first
v1.0 boot:
cd testplanit
# Records the baseline as already-applied (it matches your existing schema) —
# it is NOT re-run. The container then applies the remaining migrations.
npx zenstack migrate resolve --applied 20260625193632_init --schema schema.zmodel
After this, every later upgrade is just a normal migrate deploy (handled by
the container). A fresh database needs nothing special — the entrypoint
applies all migrations in order. See
testplanit/migrations/README.md
for details.
Feedback
Hit a problem on a beta? Open an issue and add the beta label so we can spot pre-release reports quickly.