Heym provides two scripts for different environments: run.sh for local development and deploy.sh for production deployments using Docker Compose.
Video walkthrough: Set Up Heym Locally in Under 2 Minutes — clone the repository, start PostgreSQL, fill in the required environment variables, and create your first account on a local instance.
Prerequisites
Both scripts require the following tools to be installed:
| Tool | Purpose |
|---|---|
| Docker | Database container (dev) and full stack (prod) |
| uv | Python package manager for the backend |
| bun | JavaScript runtime for the frontend |
Environment Setup
Both run.sh and deploy.sh read from a .env file in the project root. If it does not exist, the script automatically creates one from .env.example — for both local development and production.
SECRET_KEY and ENCRYPTION_KEY ship empty in .env.example. When either is empty, run.sh and deploy.sh generate a cryptographically strong value automatically and write it back to .env, so a fresh setup needs no manual key handling. If an existing .env still contains the legacy ENCRYPTION_KEY placeholder (change_this_to_a_random_32_byte_hex_value), the scripts stop with an explicit error instead of overwriting it — rotating that key would make previously-encrypted credentials unreadable. The backend itself also refuses to start if either key is empty or left at a known placeholder.
To create the file manually (optional — the scripts do this for you):
cp .env.example .envKey environment variables:
For every supported variable, default, and production note, see Environment Variables.
| Variable | Description |
|---|---|
SECRET_KEY | Required. JWT signing secret. Auto-generated by run.sh/deploy.sh when empty. |
ENCRYPTION_KEY | Required. Credential encryption key. Auto-generated by run.sh/deploy.sh when empty. |
DATABASE_URL | Optional database connection string override. If empty, Heym builds it from POSTGRES_* settings. |
BACKEND_PORT | Backend API port — defaults to 10105 |
FRONTEND_PORT | Frontend port — defaults to 4017 |
FRONTEND_URL | Required in production. Public URL of the app (scheme + host, e.g. https://heym.example.com). Used for OAuth redirect URIs (Google Sheets, BigQuery, Notion, and similar); must match the URL users use in the browser. |
ALLOW_REGISTER | Open user registration (false in prod, true in dev). Flip it to false only after your admin account exists — there is no first-user bootstrap, so an empty database plus disabled registration leaves no way to create one. |
DOCKER_LOGS_ENABLED | Enables Docker-backed Logs tab access when set to true; also requires Docker socket access |
DOCKER_LOGS_ALLOWED_EMAILS | Comma-separated list of trusted user emails allowed to access Docker logs when DOCKER_LOGS_ENABLED=true |
REQUEST_BODY_MAX_SIZE_MB | Maximum HTTP request body size accepted before endpoint handlers run; defaults to 100, one MB above FILE_MAX_SIZE_MB to leave room for multipart overhead |
HEYM_PYTHON_TOOL_SANDBOX | How user-defined Python tools run: auto (default — hardened, isolated Docker container; fail closed if Docker is unavailable), docker (same, never falls back), or subprocess (in-process local fallback; not a security boundary, trusted/dev only). run.sh sets subprocess for native dev. See Security. |
HEYM_PYTHON_TOOL_IMAGE | Image used for the Python tool Docker sandbox. Empty = auto-detect the running backend image. |
Database connection defaults (POSTGRES_HOST, POSTGRES_PORT, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB) are documented in Environment Variables and .env.example.
Development: run.sh
run.sh starts all three services locally — the database (as a Docker container), the FastAPI backend, and the Vite dev server — with a single command.
./run.shWhat it does, step by step:
- Checks that
docker,bun, anduvare available - Creates
.envfrom.env.exampleif missing, and generates a randomSECRET_KEYandENCRYPTION_KEYwhen either is empty - Starts (or creates) a database Docker container on port
6543 - Installs Python dependencies via
uv sync - Runs Alembic database migrations (
alembic upgrade head) - Frees the backend and frontend ports if occupied
- Starts the FastAPI backend with
--reload(hot-reload on code changes) - Installs frontend dependencies via
bun install - Starts the Vite dev server
Options:
| Flag | Description |
|---|---|
| (none) | Start with debug logging enabled (LOG_LEVEL=DEBUG) |
--no-debug | Start with default log level (no debug output) |
--help | Show usage information |
Service addresses (dev):
| Service | Address |
|---|---|
| Frontend | localhost on FRONTEND_PORT (default: 4017) |
| Backend API | localhost on BACKEND_PORT (default: 10105) |
| Interactive API Docs | localhost:10105/docs |
| Database | localhost:6543 |
Press Ctrl+C to gracefully stop all services.
End-to-End Tests: run_e2e.sh
The frontend Playwright suite runs against the real Vue application, FastAPI backend, and an isolated PostgreSQL database:
./run_e2e.shThe suite is a key-path smoke and regression suite, not exhaustive product coverage. It covers authentication, core workflow lifecycle and execution, selected dashboard resources, public routes, and a mocked HITL review UI. Provider integrations, every node type, sharing/permission matrices, and full HITL resume execution remain covered primarily by backend tests or require dedicated integration environments.
Each run gets its own temporary PostgreSQL 16 container, random available database/backend/frontend
ports, authentication state, test results, and HTML report. This allows multiple run_e2e.sh
processes to run concurrently without sharing state or overwriting artifacts. The script removes
its database container when the run finishes and prints the artifact directory and report path.
Useful commands:
./run_e2e.sh --ui # Interactive Playwright runner with an isolated database
cd frontend
bun run test:e2e:report # Open the most recently completed local runDirect bun run test:e2e and bun run test:e2e:ui runs require an explicit DATABASE_URL so they
cannot silently start against the local development database. Use ./run_e2e.sh for normal local
runs.
./check.sh runs lint, typecheck, formatting, and backend tests without the E2E suite, keeping the
default local check path fast. Run Playwright E2E tests separately with ./run_e2e.sh. Pull
requests always run the Chromium E2E suite in GitHub Actions and retain traces, screenshots,
videos, and the HTML report when failures occur.
Production: deploy.sh
deploy.sh builds and runs the full stack using Docker Compose. All three services — the database, the backend, and the frontend — run as containers. The backend entrypoint automatically runs migrations before starting the server with 8 workers.
Initial deploy (build + start):
./deploy.shThis performs a zero-downtime deploy: images are built first while the existing containers keep running, then the new version is swapped in.
Available commands:
| Command | Description |
|---|---|
./deploy.sh | Build images and start/update all services |
./deploy.sh --status | Show container status |
./deploy.sh --logs | Stream logs from all containers |
./deploy.sh --restart | Restart all containers |
./deploy.sh --down | Stop and remove all containers |
./deploy.sh --help | Show usage information |
Service addresses (production):
The frontend container is exposed on FRONTEND_PORT (default: 4017). The backend API is served under the /api path, proxied through the frontend container — so there is only one public-facing port in production.
Container overview:
| Container | Description |
|---|---|
heym-db | Relational database |
heym-backend | FastAPI API server (8 workers, built from backend/Dockerfile) |
heym-frontend | Frontend preview container serving the built Vue app (built from frontend/Dockerfile) |
Version update badge:
The app header shows the running Docker build version. When that version is behind the latest Heym GitHub release, a purple Update badge appears next to the version. Click the version or badge to open the Heym GitHub releases page.
Prebuilt Image: docker run
If you prefer not to build the app locally, you can pull the published container image and run it directly.
The image starts the frontend and backend together in one container. PostgreSQL is still external, but you can provide either DATABASE_URL or the POSTGRES_* variables from .env.example.
Vector store backend.
run.shanddeploy.shrun the officialpostgres:16image and auto-install thepostgresql-16-pgvectorpackage at startup, so the Postgres (pgvector) RAG backend works out of the box there with no change to your data. The prebuilt single-container image, however, connects to a PostgreSQL you provide — that database must support thevectorextension to use the Postgres backend. Heym cannot install pgvector into a database it does not manage. Without it, the startup migration skips the pgvector table gracefully — the deploy still succeeds, Qdrant RAG keeps working, and creating or uploading to a Postgres vector store returns a clear "backend unavailable" message until pgvector is enabled.
Set the keys yourself for direct image runs. Unlike run.sh/deploy.sh, the prebuilt image does not auto-generate keys. After cp .env.example .env, populate the two empty keys (replacing in place avoids duplicate entries):
SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_urlsafe(32))")
ENCRYPTION_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
sed -i.bak "s|^SECRET_KEY=.*|SECRET_KEY=${SECRET_KEY}|; s|^ENCRYPTION_KEY=.*|ENCRYPTION_KEY=${ENCRYPTION_KEY}|" .env && rm -f .env.bakdocker pull ghcr.io/heymrun/heym:latest
docker run --rm \
--env-file .env \
-p 4017:4017 \
-e FILE_STORAGE_DIR=/app/data/files \
-e HEYM_PLUGINS_DIR=/app/data/plugins \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$(pwd)/data/files:/app/data/files" \
-v "$(pwd)/data/plugins:/app/data/plugins" \
-v heym-codex-workspaces:/app/data/codex-workspaces \
ghcr.io/heymrun/heym:latestMake the file and plugin mounts absolute.
FILE_STORAGE_DIRandHEYM_PLUGINS_DIRdefault to the relative paths./data/filesanddata/plugins, and the release image starts the backend from/app/backend. Left relative, they resolve to/app/backend/data/filesand/app/backend/data/plugins, so the two bind mounts above receive nothing and uploads are lost when the container is replaced. The-eflags override the relative values.env.examplesupplies through--env-file, because command-line-ewins over--env-file. Compose (./deploy.sh) does not need this: that image runs the backend from/app, where the relative defaults already land on the mounts.
Docker socket access. Mounting
/var/run/docker.sockgives the backend broad control over the host Docker daemon. The default Docker Compose service and the directdocker runexample include it for Docker-based MCP stdio tools that rundockercommands. The Logs tab still requiresDOCKER_LOGS_ENABLED=trueandDOCKER_LOGS_ALLOWED_EMAILSwith a comma-separated list of trusted user emails. Create the trusted admin account before enabling Docker logs, or keepALLOW_REGISTER=false, so an unverified self-registration cannot claim an allow-listed email. User-defined Python tools do not get this socket: they run in a separate hardened container with no Docker socket. See Security.
Codex runner. The Codex node uses the same
ghcr.io/heymrun/heymimage as a sibling runner container (--entrypoint codex) so Codex's Linux sandbox can create namespaces. Keep theheym-codex-workspacesvolume mount if you want Codex workflows in the direct image setup; the runner does not receive the Docker socket or backend secrets.
Skill sandbox. Python skills on the Agent node run in a hardened sibling container that shares the
heym-codex-workspacesvolume, so keep that volume mount for skills too — not just Codex. Each run gets an isolated per-run subpath, and the sibling receives neither the Docker socket nor backend secrets. This needs Docker Engine 25.0+; on older engines, or without the volume,HEYM_PYTHON_TOOL_SANDBOX=autofails closed — setHEYM_PYTHON_TOOL_SANDBOX=subprocessonly for trusted single-user setups. See Security.
Playwright Run Code. Custom Playwright Python needs
HEYM_PLAYWRIGHT_CUSTOM_CODE_ENABLED=trueand the Docker socket mount above. The release image setsHEYM_PLAYWRIGHT_SANDBOX_IMAGE/HEYM_PLAYWRIGHT_SANDBOX_PYTHONfor the GHCR layout (/app/backend/.venv). Compose./deploy.shdefaults the sandbox image toheym-backend:local. Keep--no-sandboxin Chromium launch args inside sandbox containers.
Minimum environment variables for direct image runs:
| Variable | Required | Purpose |
|---|---|---|
DATABASE_URL | Optional | Full PostgreSQL connection string override |
POSTGRES_HOST | Yes, if DATABASE_URL is empty | PostgreSQL host |
POSTGRES_PORT | Yes, if DATABASE_URL is empty | PostgreSQL port |
POSTGRES_USER | Yes, if DATABASE_URL is empty | PostgreSQL username |
POSTGRES_PASSWORD | Yes, if DATABASE_URL is empty | PostgreSQL password |
POSTGRES_DB | Yes, if DATABASE_URL is empty | PostgreSQL database name |
SECRET_KEY | Yes | JWT signing secret |
ENCRYPTION_KEY | Yes | Credential encryption key |
FRONTEND_URL | Recommended | Public browser URL, especially for OAuth callbacks |
CORS_ORIGINS | Recommended | Allowed browser origins |
FILE_STORAGE_DIR | Recommended | Set it to an absolute /app/data/files when you mount that path; the relative ./data/files default resolves under /app/backend in this image and misses the mount |
ALLOW_REGISTER | Recommended | Set false in production unless open signup is intended, but only once your admin account exists — there is no first-user bootstrap |
DOCKER_LOGS_ENABLED | Optional | Set true to allow the Logs tab to use Docker socket access |
DOCKER_LOGS_ALLOWED_EMAILS | Required when DOCKER_LOGS_ENABLED=true | Comma-separated list of trusted user emails allowed to access Docker logs |
HEYM_PLUGINS_ENABLED | Optional | Set true to enable the plugin subsystem (custom nodes installed as zip). Off by default |
HEYM_PLUGIN_ADMIN_EMAILS | Required when HEYM_PLUGINS_ENABLED=true | Comma-separated operator emails allowed to install/uninstall plugins |
HEYM_PLUGINS_DIR | Optional | Where plugin files are stored. Set it to an absolute /app/data/plugins when you mount that path; the relative data/plugins default resolves under /app/backend in this image and misses the mount |
HEYM_PYTHON_TOOL_SANDBOX | Optional | Python tool isolation mode; defaults to auto (hardened Docker sandbox, fail closed). See Security |
HEYM_PYTHON_TOOL_IMAGE | Optional | Override the Python tool sandbox image; empty = auto-detect the backend image |
HEYM_CODEX_DOCKER_WORKSPACE_VOLUME | Optional | Docker volume used by sibling Codex runner containers; defaults to heym-codex-workspaces in Docker deployments |
HEYM_CODEX_NETWORK_ACCESS | Optional | Allow Codex's sandboxed commands to download files/dependencies; Docker deployments enable this by default |
Notes:
- The image exposes port
4017 - The backend stays internal and is proxied under
/api - When
POSTGRES_HOST=localhost, the release image automatically rewrites it tohost.docker.internalwhen needed so the same.envworks with a host-level PostgreSQL container on macOS Docker/Desktop tools - Keep the
data/filesmount if you want Drive uploads and skill-generated files to survive container restarts - Plugins: to enable them, set
HEYM_PLUGINS_ENABLED=trueandHEYM_PLUGIN_ADMIN_EMAILS, and mountdata/pluginsso installed plugin files persist across container recreates. Plugin metadata lives in your PostgreSQL, so it also survives. A plugin's declared pipdependenciesare installed into the container at install time; because the image filesystem is ephemeral, they are reinstalled automatically on startup for every installed plugin (the release image'suvvenv is writable, so this works in the single-container image too)
Common Workflows
First-time setup:
./run.sh # development — creates .env from .env.example and generates SECRET_KEY/ENCRYPTION_KEY automatically
# or
./deploy.sh # production — same automatic .env and key generationFor the prebuilt docker run image, generate the keys manually first (see the Prebuilt Image section above).
Update production after a code change:
git pull
./deploy.sh # rebuilds images, zero-downtime swapCheck production logs:
./deploy.sh --logsStop production services:
./deploy.sh --downRelated
- Introduction – Platform overview
- Quick Start – Build your first workflow
- Environment Variables – Full configuration reference
- Security – JWT, encryption, and CORS settings