Build from Source
TestPlanIt publishes prebuilt ghcr.io/testplanit/testplanit-selfhost images
for linux/amd64 and linux/arm64. They work on any domain, and both the
Docker Compose files and the Helm chart use them,
so most installs never build anything (see Docker Setup).
The compose files can also build the same images from the Dockerfile, so
building is a single command. Build from source when you want to:
- run a commit that hasn't been released yet
- run your own changes to TestPlanIt
- publish images to your own registry
- turn on the Next.js image optimizer (
SELF_HOSTED=false), which is fixed at build time
Trying a beta (pre-release)
Betas of the next release come from the current release branch
(release/vX.Y). Each one is published as a GitHub pre-release, such as
v1.2.0-beta.7, and as self-host images, so you don't need to build a beta
to try it. The Releases page
lists every version; pre-releases are marked Pre-release.
With Docker Compose, check out the beta's tag so the compose files match it,
then set TESTPLANIT_VERSION to the same version:
git clone https://github.com/TestPlanIt/testplanit.git
cd testplanit
git checkout v1.2.0-beta.7
TESTPLANIT_VERSION=1.2.0-beta.7 docker compose -f testplanit/docker-compose.prod.yml \
--profile with-postgres --profile with-valkey \
--profile with-elasticsearch --profile with-minio \
up -d
With Helm or docker run, use the image tags directly. A floating X.Y-beta
tag always points to the newest beta of that line (:1.2-beta and
:1.2-beta-workers); a version tag pins one beta (:1.2.0-beta.7 and
:1.2.0-beta.7-workers).
The npm packages have matching betas under the beta dist-tag, so a
client that talks to a beta server can run the same line:
npm install @testplanit/mcp-server@beta
npm install @testplanit/api@beta
The MCP server is the one to update: its beta follows the server's current
model API, so a released MCP server can fail against a beta server and the
beta MCP server needs a beta server. The Playwright and WebdriverIO
reporters keep working against a beta server unchanged. Switch back to
latest once the release ships.
To build a beta yourself, check out its tag and follow Build and run.
⚠️ Pre-releases are not production-ready. Always back up your database before trying one.
Prerequisites
- Docker and the Docker Compose plugin
- A clone (or source archive) of the version you want to build
- About 8 GB of free memory for the 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,
VALKEY_URL, and ENCRYPTION_KEY. See Docker Setup and
Environment Variables for the full configuration
reference.
Build and run
Adding --build makes the production compose file build the images from the
Dockerfile in your checkout instead of pulling them, then start the stack.
Each backing service is enabled with a profile; this command runs all of them
(Postgres, Valkey, Elasticsearch, and RustFS file storage):
docker compose -f testplanit/docker-compose.prod.yml \
--profile with-postgres --profile with-valkey \
--profile with-elasticsearch --profile with-minio \
up -d --build
Leave out a profile to use your own instance of that service instead. See
DOCKER-PROFILES.md.
The build tags its result with the published image's name
(ghcr.io/testplanit/testplanit-selfhost:latest, or the TESTPLANIT_VERSION
you set), so a later docker compose pull replaces your build with the
published image.
Images and custom domains
How TestPlanIt serves uploaded images (project icons, avatars, attachments) is
fixed at build time by the SELF_HOSTED build argument.
⚠️
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). The compose file defaults to
this setting, and the prebuilt testplanit-selfhost images are built with it. Next.js's image
optimizer is turned off, so <Image> renders a plain <img> that points
straight at your storage. One build then works on any domain with no
allowlist to configure, and BASE_DOMAIN is ignored.
Optional: SELF_HOSTED=false (Next.js image optimizer on). Turn the
optimizer back on only if you want automatic image resizing and re-encoding. You
must then set BASE_DOMAIN to your real domain. It's 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 \
--profile with-postgres --profile with-valkey \
--profile with-elasticsearch --profile with-minio \
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
The container applies pending migrations (zenstack migrate deploy) every time
it starts, so upgrading is a rebuild and restart. A database created by a
pre-1.0 release needs a one-time baseline first; see
Upgrading from a pre-1.0 release.
Feedback
Hit a problem on a beta? Open an issue
and put the exact beta version (for example, v1.2.0-beta.7) in the title.