Isoflow runs on a single host with Docker Compose: an nginx container in front, the Isoflow application container, and a PostgreSQL database. All configuration lives in one file, .env, and PostgreSQL is the only thing that holds your data.
| CPU and memory | 4 vCPU and 8 GB RAM. Diagram processing runs inside the application container, so CPU matters more than it would for a typical web app. |
|---|---|
| Disk | A few GB for the container images, plus room for your database to grow. Container logs are capped at about 100 MB each. |
| Software | Linux with Docker Engine and Docker Compose v2 (the docker compose command). |
| You need | Why |
|---|---|
| Your licence key | Issued by Isoflow for your organisation. Isoflow does not start without it, and it is also how you download the image. |
| A hostname | Users reach Isoflow at one address, such as https://diagrams.example.com. |
| An SMTP relay or provider, or Mailgun. Invitations, password resets and email verification are sent by mail, and Isoflow does not start without it while email and password sign-in is on. See the Mail section of .env. | |
| TLS (optional) | Usually handled by your own proxy or load balancer in front of Isoflow. See TLS and your own proxy. |
| A Google OAuth client (optional) | Only for “Sign in with Google”. See Sign in with Google. |
docker-compose.yml, .env and nginx/default.conf.template. Copy each one from its listing.docker login command, valid for 12 hours; run it on the Isoflow host. The same page lists the releases and, for the one you choose, gives the image line to put in docker-compose.yml in place of the placeholder..env:LICENSE_KEY: your licence key.APP_ORIGIN: the address users will open.AUTH_SECRET: generate it with openssl rand -base64 32.DB_PASSWORD: generate it with openssl rand -hex 24.CONTACT_EMAIL and CONTACT_NAME: the address and name your users see. Every email Isoflow sends comes from it, so your mail relay must accept it as a sender.SUPERUSER_EMAILS: the email address of your first administrator, usually yours, or several separated by commas. Isoflow invites each to your workspace.SMTP_HOST, in the Mail section: your mail relay. Add SMTP_PORT if it is not 587, and SMTP_USER and SMTP_PASSWORD if it needs a login.docker compose up -ddocker compose logs appSUPERUSER_EMAILS: open your APP_ORIGIN, choose Sign up and use that address, with a password or with Google if you set it up. A password sign-up sends you an email to confirm the address, and you join your workspace when you first sign in after confirming it; a Google sign-up joins at once, as Google has confirmed the address. You are the workspace’s administrator, and you can now invite everyone else.SUPERUSER_EMAILS as soon as Isoflow is running.Until an address has signed up, anyone who can reach Isoflow can start a sign-up with it and choose its password. If you are told the address already has an account that you didn’t create, don’t use any verification link: reset the password first, then verify.On every start, Isoflow checks its licence and configuration, waits up to a minute for the database to answer, applies any database migrations, sets up your workspace and then begins serving.
These are the files Isoflow is tested with. The comments in each one explain every setting.
Three services: nginx, the application and PostgreSQL. Only nginx publishes a port on the host; the application and the database are reachable only from inside the Compose network.
# Isoflow self-hosted. Put this file, .env and nginx/default.conf.template in one
# folder, fill in .env, then run: docker compose up -d
# The full guide is at /docs/self-hosted/install-guide on any Isoflow install.
# Docker keeps roughly the last 100 MB of each container's output.
x-logging: &logging
driver: json-file
options:
max-size: 20m
max-file: "5"
services:
nginx:
image: nginx:1.29.8
logging: *logging
restart: unless-stopped
ports:
- "${HTTP_PORT:-80}:80"
# To serve HTTPS directly, see "Enabling TLS" in the install guide:
# - "443:443"
volumes:
- ./nginx/default.conf.template:/etc/nginx/templates/default.conf.template:ro
# - ./certs:/etc/nginx/certs:ro
# - ./certbot:/var/www/certbot:ro
depends_on:
- app
app:
# Replace this line with the image line from the download page. To
# upgrade, change the version and run `docker compose up -d`.
image: <registry>/isoflow/app:<version>
logging: *logging
restart: unless-stopped
init: true
env_file: .env
# With your own Postgres instead of the bundled one, delete this
# depends_on and the db service below.
depends_on:
db:
condition: service_healthy
db:
image: postgres:18.4-alpine
logging: *logging
restart: unless-stopped
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
volumes:
- db:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""]
interval: 5s
timeout: 5s
retries: 12
volumes:
db:
Your configuration. Save it as .env beside docker-compose.yml. The required block comes first; every other setting has a working default, and every settingis described below. Settings not listed here belong to Isoflow's hosted service: leave them out. In particular, BILLING_PROVIDER, the PADDLE_ settings and ENABLE_ICON_GENERATION=true stop a self-hosted install from starting.
# Isoflow self-hosted configuration. Save this as .env beside
# docker-compose.yml and fill in the required block. Every setting is read at
# start-up: after a change, run `docker compose up -d` again. The boot report
# at the top of `docker compose logs app` shows what is enabled and why.
# ---------------------------------------------------------------------------
# Required
# ---------------------------------------------------------------------------
# Your licence key. Without a valid one Isoflow does not start, and the log
# says whether it is missing, damaged or not issued for this version.
LICENSE_KEY=
# The address your users open, with no path. Sign-in cookies and the links in
# emails are built from it, so it must match the address in the browser
# exactly, or sign-in fails. The boot check refuses a value with a path.
APP_ORIGIN=https://diagrams.example.com
# Signs user sessions; at least 32 characters, or Isoflow does not start.
# Generate one with:
# openssl rand -base64 32
# Changing it signs everyone out.
AUTH_SECRET=
# The database password. For the bundled database, generate one with:
# openssl rand -hex 24
# The bundled database takes it on its first start only; to change it later,
# change it in Postgres too.
DB_PASSWORD=
# Your contact address, e.g. [email protected]. Every email Isoflow sends
# comes from it, and users are pointed to it for help. Your mail relay must
# accept it as a sender, or every email fails with "Failed to send email." in
# the log. The boot check refuses a malformed address.
CONTACT_EMAIL=
# Your contact name, e.g. Acme Diagrams: the sender name on every email. The
# boot check refuses <, >, " and line breaks.
CONTACT_NAME=
# Your superusers' email addresses, comma-separated: usually just yours, e.g.
# [email protected], or [email protected],[email protected]. Superusers can open
# the admin dashboard, and are exactly the accounts listed here: change the
# list and restart, and the role follows. On each start Isoflow invites every
# address that is not yet in your workspace, so sign up with it to join. The
# boot check refuses a malformed address.
SUPERUSER_EMAILS=
# ---------------------------------------------------------------------------
# Database
# ---------------------------------------------------------------------------
# These point at the bundled Postgres container. For your own Postgres, set
# them to your server and remove the db service from docker-compose.yml. If
# Isoflow cannot reach the database within 60 seconds, or cannot sign in to it,
# it does not start.
# The Postgres server.
DB_HOST=db
# The Postgres port. Default: 5432.
DB_PORT=5432
# The Postgres user.
DB_USER=isoflow
# The Postgres database.
DB_NAME=isoflow
# ---------------------------------------------------------------------------
# Network
# ---------------------------------------------------------------------------
# The host port nginx publishes, read by docker-compose.yml. If another service
# holds it, the nginx container does not start. Default: 80.
# HTTP_PORT=80
# ---------------------------------------------------------------------------
# Mail
# ---------------------------------------------------------------------------
# Invitations, password resets and email verification are sent by mail, from
# CONTACT_NAME and CONTACT_EMAIL in the required block. With email and password
# sign-in on (the default), Isoflow does not start without mail. With Google
# sign-in only it starts and warns, but members cannot be invited.
# smtp for your mail relay or provider, mailgun for Mailgun's API, or none.
# Fill in the settings for the one you choose. Default: none.
MAIL_PROVIDER=smtp
# Your mail relay. If it cannot be reached, emails fail with "Failed to send
# email." in the log. Required with MAIL_PROVIDER=smtp.
SMTP_HOST=
# Port 465 uses TLS from the start; any other port switches to TLS (STARTTLS)
# when the relay offers it. If the relay's certificate comes from your own
# certificate authority, mount the CA file into the app container and set
# NODE_EXTRA_CA_CERTS to its path. Default: 587.
# SMTP_PORT=587
# Set it and SMTP_PASSWORD together, or leave both unset for a relay that
# accepts this host without a login, often on port 25. One without the other
# stops the start; wrong credentials make every email fail.
# SMTP_USER=
# SMTP_PASSWORD=
# Your Mailgun sending domain. Required with MAIL_PROVIDER=mailgun.
# MAILGUN_DOMAIN=
# Your Mailgun API key. A wrong one makes every email fail.
# Required with MAIL_PROVIDER=mailgun.
# MAILGUN_API_KEY=
# https://api.mailgun.net, or https://api.eu.mailgun.net for the EU region. The
# wrong region makes every email fail. Required with MAIL_PROVIDER=mailgun.
# MAILGUN_API_BASE=
# ---------------------------------------------------------------------------
# Sign in with Google (optional)
# ---------------------------------------------------------------------------
# Set it and GOOGLE_OAUTH_CLIENT_SECRET to offer "Sign in with Google"; with
# only one, Google stays off and the boot report warns. The redirect URI to
# register with Google is printed in the boot report:
# <APP_ORIGIN>/api/auth/callback/google
# GOOGLE_OAUTH_CLIENT_ID=
# GOOGLE_OAUTH_CLIENT_SECRET=
# false makes Google the only way to sign in: the email and password form,
# sign-up and password reset are removed. The boot check refuses false unless
# both Google settings are set. Users who already signed up with an email and
# password cannot then sign in at all, so set it before anyone signs up.
# Default: true.
# ENABLE_EMAIL_PASSWORD_AUTH=true
# ---------------------------------------------------------------------------
# Logging (optional)
# ---------------------------------------------------------------------------
# container writes logs to the container output, with fields holding passwords,
# tokens, keys or email addresses redacted. sentry sends server and browser
# errors to your own Sentry project. Default: container.
# LOGGING_PROVIDER=container
# Your Sentry project's DSN. It is sent to your users' browsers. Set
# without LOGGING_PROVIDER=sentry, it is ignored with a warning.
# Required with LOGGING_PROVIDER=sentry.
# SENTRY_DSN=
# ---------------------------------------------------------------------------
# Analytics (optional)
# ---------------------------------------------------------------------------
# A Google Analytics 4 measurement ID (G-…) to track page views in your users'
# browsers, which then load Google's tag. Left out, there are no analytics.
# Isoflow shows no cookie-consent banner, so your users' consent is up to you.
# The boot check refuses anything but a G- ID.
# GOOGLE_ANALYTICS_ID=
# ---------------------------------------------------------------------------
# Diagram workers (optional tuning)
# ---------------------------------------------------------------------------
# Diagram operations run in a pool of worker threads inside the app container.
# pool, or none to run them on the main thread; the settings below are then
# ignored with a warning. Default: pool.
# WORKERS_PROVIDER=pool
# Threads in the pool, at least 1. More than the host's CPU cores warns, and
# adds memory, not speed. Default: 2.
# WORKERS_POOL_SIZE=2
# How long one operation may wait for a free thread before the user is told the
# server is busy, in milliseconds; at least 1000. Default: 15000.
# WORKERS_QUEUE_TIMEOUT_MS=15000
# How long one operation may run before it is stopped, in milliseconds; at
# least 1000. Very large diagrams need longer. Default: 15000.
# WORKERS_RUN_TIMEOUT_MS=15000
# Operations that may wait per thread before new ones are refused; at least 1.
# Default: 50.
# WORKERS_QUEUE_PER_WORKER=50
# Memory limit per thread, in megabytes; at least 256. Default: 512.
# WORKERS_MAX_HEAP_MB=512
Gives Isoflow a single address: the API, assets and discovery paths go to the API server, everything else to the web app. It serves plain HTTP on port 80 unless you enable TLS.
# Isoflow's single origin: /api, /assets and the MCP discovery paths go to the
# api, everything else to the web app. Plain HTTP on port 80 by default, for a
# proxy or load balancer in front that terminates TLS. To serve HTTPS from here
# instead, see "Enabling TLS" in the install guide.
map $http_upgrade $connection_upgrade {
websocket upgrade;
default "";
}
# Behind a proxy, pass on the scheme it reports; otherwise the one nginx saw.
map $http_x_forwarded_proto $forwarded_proto {
default $http_x_forwarded_proto;
"" $scheme;
}
server {
listen 80 default_server;
# For HTTPS, replace the listen line above with these three, and uncomment
# the redirect server at the end of this file:
# listen 443 ssl default_server;
# ssl_certificate /etc/nginx/certs/fullchain.pem;
# ssl_certificate_key /etc/nginx/certs/privkey.pem;
server_name _;
large_client_header_buffers 4 32k;
client_header_buffer_size 32k;
# Each edit sends the whole diagram, and Isoflow accepts up to 2 MB; a
# lower value here stops large diagrams from being edited or saved.
client_max_body_size 2m;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $forwarded_proto;
# Let's Encrypt HTTP-01 challenges, answered from a mounted folder.
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# OAuth discovery for the MCP server, answered by the api when MCP is on.
location /.well-known/ {
proxy_pass http://app:4000;
}
location /assets {
proxy_pass http://app:4000;
}
location /api {
proxy_pass http://app:4000/api;
}
location / {
proxy_pass http://app:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $forwarded_proto;
}
}
# With HTTPS enabled above, plain HTTP only answers Let's Encrypt and redirects.
# server {
# listen 80 default_server;
# server_name _;
#
# location /.well-known/acme-challenge/ {
# root /var/www/certbot;
# }
#
# location / {
# return 301 https://$host$request_uri;
# }
# }
Edit .env, then apply the change. This recreates the application container, which briefly interrupts users:
docker compose up -dThese are the settings in .env, in the same order and with the same descriptions. A setting that cannot work stops Isoflow from starting, and the log names it.
| Setting | Default | What it does, and what happens if it is wrong |
|---|---|---|
| Required | ||
LICENSE_KEY | Required | Your licence key. Without a valid one Isoflow does not start, and the log says whether it is missing, damaged or not issued for this version. |
APP_ORIGIN | Required | The address your users open, with no path. Sign-in cookies and the links in emails are built from it, so it must match the address in the browser exactly, or sign-in fails. The boot check refuses a value with a path. |
AUTH_SECRET | Required | Signs user sessions; at least 32 characters, or Isoflow does not start. Generate one with: openssl rand -base64 32 Changing it signs everyone out. |
DB_PASSWORD | Required | The database password. For the bundled database, generate one with: openssl rand -hex 24 The bundled database takes it on its first start only; to change it later, change it in Postgres too. |
CONTACT_EMAIL | Required | Your contact address, e.g. [email protected]. Every email Isoflow sends comes from it, and users are pointed to it for help. Your mail relay must accept it as a sender, or every email fails with "Failed to send email." in the log. The boot check refuses a malformed address. |
CONTACT_NAME | Required | Your contact name, e.g. Acme Diagrams: the sender name on every email. The boot check refuses <, >, " and line breaks. |
SUPERUSER_EMAILS | Required | Your superusers' email addresses, comma-separated: usually just yours, e.g. [email protected], or [email protected],[email protected]. Superusers can open the admin dashboard, and are exactly the accounts listed here: change the list and restart, and the role follows. On each start Isoflow invites every address that is not yet in your workspace, so sign up with it to join. The boot check refuses a malformed address. |
| Database | ||
| These point at the bundled Postgres container. For your own Postgres, set them to your server and remove the db service from docker-compose.yml. If Isoflow cannot reach the database within 60 seconds, or cannot sign in to it, it does not start. | ||
DB_HOST | Required | The Postgres server. |
DB_PORT | 5432 | The Postgres port. |
DB_USER | Required | The Postgres user. |
DB_NAME | Required | The Postgres database. |
| Network | ||
HTTP_PORT | 80 | The host port nginx publishes, read by docker-compose.yml. If another service holds it, the nginx container does not start. |
| Invitations, password resets and email verification are sent by mail, from CONTACT_NAME and CONTACT_EMAIL in the required block. With email and password sign-in on (the default), Isoflow does not start without mail. With Google sign-in only it starts and warns, but members cannot be invited. | ||
MAIL_PROVIDER | none | smtp for your mail relay or provider, mailgun for Mailgun's API, or none. Fill in the settings for the one you choose. |
SMTP_HOST | Required with MAIL_PROVIDER=smtp | Your mail relay. If it cannot be reached, emails fail with "Failed to send email." in the log. |
SMTP_PORT | 587 | Port 465 uses TLS from the start; any other port switches to TLS (STARTTLS) when the relay offers it. If the relay's certificate comes from your own certificate authority, mount the CA file into the app container and set NODE_EXTRA_CA_CERTS to its path. |
SMTP_USER | None | Set it and SMTP_PASSWORD together, or leave both unset for a relay that accepts this host without a login, often on port 25. One without the other stops the start; wrong credentials make every email fail. |
SMTP_PASSWORD | None | See SMTP_USER. |
MAILGUN_DOMAIN | Required with MAIL_PROVIDER=mailgun | Your Mailgun sending domain. |
MAILGUN_API_KEY | Required with MAIL_PROVIDER=mailgun | Your Mailgun API key. A wrong one makes every email fail. |
MAILGUN_API_BASE | Required with MAIL_PROVIDER=mailgun | https://api.mailgun.net, or https://api.eu.mailgun.net for the EU region. The wrong region makes every email fail. |
| Sign in with Google (optional) | ||
GOOGLE_OAUTH_CLIENT_ID | None | Set it and GOOGLE_OAUTH_CLIENT_SECRET to offer "Sign in with Google"; with only one, Google stays off and the boot report warns. The redirect URI to register with Google is printed in the boot report: <APP_ORIGIN>/api/auth/callback/google |
GOOGLE_OAUTH_CLIENT_SECRET | None | See GOOGLE_OAUTH_CLIENT_ID. |
ENABLE_EMAIL_PASSWORD_AUTH | true | false makes Google the only way to sign in: the email and password form, sign-up and password reset are removed. The boot check refuses false unless both Google settings are set. Users who already signed up with an email and password cannot then sign in at all, so set it before anyone signs up. |
| Logging (optional) | ||
LOGGING_PROVIDER | container | container writes logs to the container output, with fields holding passwords, tokens, keys or email addresses redacted. sentry sends server and browser errors to your own Sentry project. |
SENTRY_DSN | Required with LOGGING_PROVIDER=sentry | Your Sentry project's DSN. It is sent to your users' browsers. Set without LOGGING_PROVIDER=sentry, it is ignored with a warning. |
| Analytics (optional) | ||
GOOGLE_ANALYTICS_ID | None | A Google Analytics 4 measurement ID (G-…) to track page views in your users' browsers, which then load Google's tag. Left out, there are no analytics. Isoflow shows no cookie-consent banner, so your users' consent is up to you. The boot check refuses anything but a G- ID. |
| Diagram workers (optional tuning) | ||
| Diagram operations run in a pool of worker threads inside the app container. | ||
WORKERS_PROVIDER | pool | pool, or none to run them on the main thread; the settings below are then ignored with a warning. |
WORKERS_POOL_SIZE | 2 | Threads in the pool, at least 1. More than the host's CPU cores warns, and adds memory, not speed. |
WORKERS_QUEUE_TIMEOUT_MS | 15000 | How long one operation may wait for a free thread before the user is told the server is busy, in milliseconds; at least 1000. |
WORKERS_RUN_TIMEOUT_MS | 15000 | How long one operation may run before it is stopped, in milliseconds; at least 1000. Very large diagrams need longer. |
WORKERS_QUEUE_PER_WORKER | 50 | Operations that may wait per thread before new ones are refused; at least 1. |
WORKERS_MAX_HEAP_MB | 512 | Memory limit per thread, in megabytes; at least 256. |
Each time the application starts, it writes one block to its log describing what it understood from your configuration:
Isoflow Diagramming Platform · v4.1.0
edition self-hosted
licence Acme Ltd · valid until 2027-10-03 · id 3f9c2a7e-…
origin https://diagrams.example.com
database db:5432/isoflow
storage local
mail smtp · mail.example.com:587 (STARTTLS if offered, auth: isoflow)
billing none
sign-in email/password · google
google redirect URI → https://diagrams.example.com/api/auth/callback/google
superusers [email protected]
workers pool · 2 threads, 15s to wait, 15s to run
logging container (stdout/stderr)
icon gen disabledAfter applying database migrations, it adds two lines about your workspace and your superuser:
workspace Acme Ltd · created 2026-10-11 · invite-only
superusers [email protected] · invited; sign up as this address to joinIf the configuration cannot work, Isoflow does not start. Instead it lists every problem it found, so you can fix them all in one pass:
Isoflow cannot start. Fix the following and restart:
LICENSE_KEY is malformed; check it was copied whole, with no line breaks
AUTH_SECRET must be at least 32 characters; generate one with: openssl rand -base64 32Isoflow is single-tenant on the self-hosted edition. A workspace is created as part of first-time setup.
SUPERUSER_EMAILS first.SUPERUSER_EMAILS: edit the list and restart, and the role follows it, added and removed.SUPERUSER_EMAILS: if it is not in the workspace, Isoflow invites it on the next start. Removing an address leaves its invitation, if unused, until an administrator deletes it.Users must reach Isoflow at a single address. By default nginx serves plain HTTP on port 80 (or HTTP_PORT), for a proxy or load balancer in front of it that terminates TLS.
Forward all traffic for the hostname to nginx on the host, and:
X-Forwarded-Proto header to https;/api if you want protection against password guessing. Isoflow does not limit requests itself.certs folder beside docker-compose.yml, as fullchain.pem and privkey.pem.docker-compose.yml, uncomment the "443:443" port and the ./certs:/etc/nginx/certs:ro volume.nginx/default.conf.template, replace listen 80 default_server;with the three commented lines beneath it, and uncomment the redirect server at the end of the file. Plain HTTP then redirects to HTTPS, except Let's Encrypt challenges.APP_ORIGIN to your https:// address and run docker compose up -d.For a hostname reachable from the internet on port 80, certbot can issue a certificate through nginx. Uncomment the ./certbot:/var/www/certbot:ro volume in docker-compose.yml and run docker compose up -d first, then:
mkdir -p certbot letsencrypt certs
docker run --rm \
-v "$PWD/certbot:/var/www/certbot" \
-v "$PWD/letsencrypt:/etc/letsencrypt" \
certbot/certbot certonly --webroot -w /var/www/certbot \
-d diagrams.example.com --email [email protected] --agree-tos --no-eff-email
cp -L letsencrypt/live/diagrams.example.com/fullchain.pem certs/
cp -L letsencrypt/live/diagrams.example.com/privkey.pem certs/Then follow Enabling TLS in nginx above. Certificates last 90 days; renew them on a schedule, such as a monthly cron job:
docker run --rm \
-v "$PWD/certbot:/var/www/certbot" \
-v "$PWD/letsencrypt:/etc/letsencrypt" \
certbot/certbot renew --webroot -w /var/www/certbot
cp -L letsencrypt/live/diagrams.example.com/fullchain.pem certs/
cp -L letsencrypt/live/diagrams.example.com/privkey.pem certs/
docker compose exec nginx nginx -s reloadBy default, users sign in with an email address and password. To add “Sign in with Google”:
<APP_ORIGIN>/api/auth/callback/google. The boot report prints this address for your installation; copy it from there. A mismatch here is the most common sign-in problem.GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET in .env and apply the change.Google sign-in needs outbound access from the host to accounts.google.com and oauth2.googleapis.com.
To make Google the only way to sign in, also set ENABLE_EMAIL_PASSWORD_AUTH=false. The email and password form, sign-up and password reset disappear, and the boot check refuses the setting unless both Google variables are set. Decide before anyone signs up: users who already have an email and password cannot sign in once it is on, not even with Google under the same address.
PostgreSQL is the only place Isoflow stores data. Images, icon packs and 3D models are part of the application image: they are replaced when you upgrade, and Isoflow writes no files at runtime.
To use your own PostgreSQL server instead of the bundled one, set the DB_* settings in .env to point at it, and delete the db service and the application's depends_on from docker-compose.yml. Backups are then done with your own tooling.
Backups are yours to schedule; keep copies off the host. With the bundled database, this writes a dump to the current folder:
docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" --format=custom' > isoflow-$(date +%F).dumpTo restore a dump, stop the application while the database is replaced:
docker compose stop app
docker compose exec -T db sh -c 'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists --no-owner' < isoflow-2026-10-10.dump
docker compose start appYou can restore a dump taken on an older version into a newer one: Isoflow migrates the database forward when it starts.
image line of your docker-compose.yml. Compare the rest of the file, and the nginx template, with The files too, in case the release changed them.docker login has expired, run the new one the download page gives you.docker compose pull app
docker compose up -dIsoflow migrates the database as it starts. You can upgrade from any earlier version straight to the latest in one step. Upgrading recreates the application container, which discards its logs.
If the Isoflow host cannot reach the image registry, pull the images on a machine that can and move them across as a file, using the image from the download page:
# On a machine that can reach the registry, after docker login:
docker pull <registry>/isoflow/app:<version>
docker save -o isoflow-images.tar <registry>/isoflow/app:<version> postgres:18.4-alpine nginx:1.29.8
# Copy isoflow-images.tar to the Isoflow host, then:
docker load -i isoflow-images.tarFully air-gapped installations are not supported: sign-in with Google needs outbound access, for example.
docker compose down # stops and removes the containers; keeps your database
docker compose down -v # also deletes the database volume, permanentlyTake a backup before docker compose down -v. It cannot be undone.
Isoflow needs a valid licence key to run. The key is issued for your organisation and expires on a set date, usually a year after issue. It is checked entirely on your host; Isoflow never contacts us about it.
| Situation | What happens |
|---|---|
| Missing or invalid key | Isoflow does not start. The log says which problem it found: missing, damaged, or not issued for this version. |
| Expired, within the grace period | Everything keeps working, usually for 30 days. Workspace admins see a warning in workspace settings, and the boot report warns, both with the date Isoflow will shut down. |
| Grace period over | Isoflow shuts down at 01:00 UTC on the day after the grace period ends, and will not start again until the key is renewed. The log names LICENSE_KEY with the expiry date and the date the grace period ended. |
To renew, replace LICENSE_KEY in .env with your new key and run docker compose up -d. After the grace period, Docker keeps trying to restart the container and each attempt stops at the licence check; renewing the key is the only fix needed, and no data is affected.
docker compose ps # is each container running?
docker compose logs app # application logs, including the boot report
docker compose logs nginx # requests and proxy errors
curl http://localhost/api/health # answers {"status":"ok", ...} when healthyLogs are size-capped, and do not survive an upgrade. Docker keeps roughly the last 100 MB of output for each container, and recreating a container discards its logs. For longer history, change the logging driver in the x-logging block at the top of docker-compose.yml to send logs to your own system, such as syslog, journald or a log aggregator.
The API writes one JSON object per line: the time, the level (error, warn or info), the message, and for a request, its id, method, route and user id. While a superuser impersonates someone, the user id is the superuser’s, and impersonatingholds the other user’s. An error includes its stack and the error that caused it. Fields holding passwords, tokens, keys or email addresses are redacted. Every response carries an X-Request-Idheader: a user who quotes it lets you find that request’s lines. The boot report stays a readable block.
| Symptom | Likely cause |
|---|---|
| “Isoflow cannot start: …” | A configuration problem, one line for each, naming the setting to fix. |
| “the database … did not answer within 60s” | The database is not running or not reachable at DB_HOST and DB_PORT. Check docker compose ps. |
| “the database … refused the connection” | Wrong DB_USER, DB_PASSWORD or DB_NAME. The bundled database takes its password on its first start only. |
| “Isoflow cannot start: found 2 workspaces” | An installation has exactly one workspace, and the database holds more, for example after restoring a backup from another installation. Contact support. |
| “Failed to send email.” when inviting or signing up | The mail relay could not be reached or refused the message; docker compose logs app shows its reason. Check SMTP_HOST, SMTP_PORT and the login, that the relay accepts CONTACT_EMAIL as a sender, and, for a certificate error, NODE_EXTRA_CA_CERTS. |
/api/health answers 503 with "status":"unavailable", and diagrams cannot be edited | The diagram workers failed to start. docker compose logs app shows the error; include it when you contact support. |
| nginx returns 502 Bad Gateway | The application is not running. Check docker compose logs app. |
Google sign-in fails with redirect_uri_mismatch | The redirect URI in Google Cloud does not exactly match the one in the boot report. |
| “Ask your administrator for an invitation” when signing up or signing in | The address has no invitation, or the account is not in the workspace. An administrator can invite it, including someone who was removed: removal deleted their account. |
| Users are signed out after a configuration change | AUTH_SECRET changed. Sessions signed with the old secret are no longer valid. |
.env takes effect when the application container is recreated.When you contact support, include:
docker compose logs app, which contains your licence id;What support covers is set out in your agreement.

A Revarex tool.
Copyright © 2026 Revarex Limited.
Support
Need help? Contact us