CukesStudio is a workspace for Gherkin .feature
files — a linting editor that holds every scenario to your house standard,
a review workflow, and a way to see, across thousands of scenarios, which
ones actually matter.
This image runs the whole stack on your own infrastructure — auth, data, and everything you write stays on your machine.
The one exception: the api container sends a daily anonymous ping
(workspace/feature/scenario/user counts only — never file contents, never
names) to help us gauge adoption. Set ANALYTICS_ENABLED=false in .env
to turn that off.
Save this as docker-compose.yml:
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: cukestudio
POSTGRES_USER: cukestudio
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
volumes:
- cukestudio-db:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U cukestudio -d cukestudio']
interval: 5s
timeout: 5s
retries: 10
api:
image: suirtech/cukesstudio-api:${CUKESTUDIO_VERSION:-latest}
depends_on:
db:
condition: service_healthy
environment:
DB_DRIVER: pg
DATABASE_URL: postgres://cukestudio:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}@db:5432/cukestudio
JWT_SECRET: ${JWT_SECRET:?set JWT_SECRET in .env}
PORT: 3000
COGNITO_USER_POOL_ID: unused-self-hosted
REGISTER_USERNAME: ${REGISTER_USERNAME:-}
REGISTER_PASSWORD: ${REGISTER_PASSWORD:-}
ANALYTICS_ENABLED: ${ANALYTICS_ENABLED:-true}
ports:
- '3000:3000'
web:
image: suirtech/cukesstudio-web:${CUKESTUDIO_VERSION:-latest}
depends_on:
- api
ports:
- '8080:80'
volumes:
cukestudio-db:
And this as .env:
POSTGRES_PASSWORD=changeme
JWT_SECRET=changeme
(Use real generated secrets for anything reachable outside your own machine.)
Then:
docker compose pull
docker compose up -d
Once db reports healthy and api/web are up, create your first
account:
docker compose exec api node dist/create-user.js [email protected] --admin
This prints a temporary password — there's no self-serve password change yet, so keep it somewhere safe. Open http://localhost:8080 and sign in.
Either the CLI again (docker compose exec api node dist/create-user.js <email> [--admin],
or --reset-password to rotate one), or set REGISTER_USERNAME/
REGISTER_PASSWORD in .env to turn on a signup form at
http://localhost:8080/register, protected by HTTP Basic Auth with that
exact pair — lets a team admin add accounts without shell access to the
container.
docker compose pull
docker compose up -d
Schema migrations run automatically on api startup — safe to pull and
restart at any time. Pin CUKESTUDIO_VERSION in .env (e.g. 1.0.0)
instead of always running the newest image if you'd rather upgrade
deliberately.
The in-app admin panel doesn't work self-hosted — user management is the
CLI or the /register form above, not the UI's admin screen.
Any Postgres client works against db (cukestudio / your
POSTGRES_PASSWORD / database cukestudio). To run one alongside the
stack, add this service to docker-compose.yml:
pgadmin:
image: dpage/pgadmin4:9.17
depends_on:
db:
condition: service_healthy
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL:?set PGADMIN_EMAIL in .env}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD:?set PGADMIN_PASSWORD in .env}
volumes:
- cukestudio-pgadmin:/var/lib/pgadmin
ports:
- '8081:80'
(add cukestudio-pgadmin: under volumes:, and PGADMIN_EMAIL/
PGADMIN_PASSWORD to .env) then add the db server by hand from
pgAdmin's UI the first time — host db, port 5432.
Self-hosting is free, with no license key and no usage limits — the one thing we ask in return is a small, anonymous signal that it's actually being used, so we know whether to keep investing in it.
The api container pings CukesStudio once a day with aggregate counts —
number of workspaces, feature files, scenarios, and user accounts — plus a
random id it mints for itself on first boot (instance_meta table) and the
version it's running. It never sends file contents, scenario text,
workspace/team names, or anything tied to a person or account. Set
ANALYTICS_ENABLED=false in .env to turn it off entirely; a failed or
blocked ping (offline/air-gapped installs included) never affects the
running app.
The web container proxies /api/* to the api container over the compose
network (infra/self-hosted/nginx.conf), so the browser only ever talks to
one origin and there's no CORS configuration to get right. If you're putting
this behind your own reverse proxy or domain, keep that same-origin
/api proxy in front of it, or the frontend's config.json (baked into the
web image from infra/self-hosted/config.local.json) needs to change too.
Content type
Image
Digest
sha256:201b23b3f…
Size
21.5 MB
Last updated
about 1 month ago
docker pull suirtech/cukesstudio-web