Installing Isoflow (self-hosted)

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.

1. Before you start

Host requirements

CPU and memory4 vCPU and 8 GB RAM. Diagram processing runs inside the application container, so CPU matters more than it would for a typical web app.
DiskA few GB for the container images, plus room for your database to grow. Container logs are capped at about 100 MB each.
SoftwareLinux with Docker Engine and Docker Compose v2 (the docker compose command).

What you need

You needWhy
Your licence keyIssued by Isoflow for your organisation. Isoflow does not start without it, and it is also how you download the image.
A hostnameUsers reach Isoflow at one address, such as https://diagrams.example.com.
MailAn 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.

2. Install

  1. Create a folder holding the three files shown in The files: docker-compose.yml, .env and nginx/default.conf.template. Copy each one from its listing.
  2. Log in to the image registry. On the download page, enter your licence key. It gives you a 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.
  3. Fill in the required block of .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.
  4. Start Isoflow from that folder:
    docker compose up -d
  5. Read the boot report to check your configuration was understood (see Reading the boot report):
    docker compose logs app
  6. Sign up with your address from SUPERUSER_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.
  7. Do this for every address in 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.

3. The files

These are the files Isoflow is tested with. The comments in each one explain every setting.

docker-compose.yml

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.

docker-compose.yml
# 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:

.env

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.

.env
# 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

nginx/default.conf.template

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.

nginx/default.conf.template
# 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;
#     }
# }

4. Changing the configuration

Edit .env, then apply the change. This recreates the application container, which briefly interrupts users:

docker compose up -d

Every setting

These 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.

SettingDefaultWhat it does, and what happens if it is wrong
Required
LICENSE_KEYRequiredYour 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_ORIGINRequiredThe 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_SECRETRequiredSigns user sessions; at least 32 characters, or Isoflow does not start. Generate one with: openssl rand -base64 32 Changing it signs everyone out.
DB_PASSWORDRequiredThe 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_EMAILRequiredYour 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_NAMERequiredYour contact name, e.g. Acme Diagrams: the sender name on every email. The boot check refuses <, >, " and line breaks.
SUPERUSER_EMAILSRequiredYour 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_HOSTRequiredThe Postgres server.
DB_PORT5432The Postgres port.
DB_USERRequiredThe Postgres user.
DB_NAMERequiredThe Postgres database.
Network
HTTP_PORT80The host port nginx publishes, read by docker-compose.yml. If another service holds it, the nginx container does not start.
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.
MAIL_PROVIDERnonesmtp for your mail relay or provider, mailgun for Mailgun's API, or none. Fill in the settings for the one you choose.
SMTP_HOSTRequired with MAIL_PROVIDER=smtpYour mail relay. If it cannot be reached, emails fail with "Failed to send email." in the log.
SMTP_PORT587Port 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_USERNoneSet 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_PASSWORDNoneSee SMTP_USER.
MAILGUN_DOMAINRequired with MAIL_PROVIDER=mailgunYour Mailgun sending domain.
MAILGUN_API_KEYRequired with MAIL_PROVIDER=mailgunYour Mailgun API key. A wrong one makes every email fail.
MAILGUN_API_BASERequired with MAIL_PROVIDER=mailgunhttps://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_IDNoneSet 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_SECRETNoneSee GOOGLE_OAUTH_CLIENT_ID.
ENABLE_EMAIL_PASSWORD_AUTHtruefalse 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_PROVIDERcontainercontainer 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_DSNRequired with LOGGING_PROVIDER=sentryYour 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_IDNoneA 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_PROVIDERpoolpool, or none to run them on the main thread; the settings below are then ignored with a warning.
WORKERS_POOL_SIZE2Threads in the pool, at least 1. More than the host's CPU cores warns, and adds memory, not speed.
WORKERS_QUEUE_TIMEOUT_MS15000How 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_MS15000How long one operation may run before it is stopped, in milliseconds; at least 1000. Very large diagrams need longer.
WORKERS_QUEUE_PER_WORKER50Operations that may wait per thread before new ones are refused; at least 1.
WORKERS_MAX_HEAP_MB512Memory limit per thread, in megabytes; at least 256.

5. Reading the boot report

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      disabled

After 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 join
  • Any warnings follow the last line. They do not stop Isoflow, but each one names a feature that is off or degraded.
  • When you contact support, include the whole block. Its licence id identifies your installation.

If 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 32

6. Users

Isoflow is single-tenant on the self-hosted edition. A workspace is created as part of first-time setup.

  • Inviting users. A workspace administrator invites people from the workspace settings. They receive an email with a link and sign up with the invited address. Once the address is confirmed (by email for a password, at once with Google), they join the workspace.
  • Removing users. Removing someone from the workspace deletes their account. Diagrams they shared move to the administrator who removed them; the rest are deleted with the account. Deleting an account from the admin dashboard deletes all of its diagrams, shared ones included. Superusers are always administrators: they cannot be removed or given another seat, so take them out of SUPERUSER_EMAILS first.
  • Superusers can open the admin dashboard. They are exactly the accounts whose addresses are in SUPERUSER_EMAILS: edit the list and restart, and the role follows it, added and removed.
  • Adding an address to 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.

7. TLS and your own proxy

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.

Your own proxy or load balancer (recommended)

Forward all traffic for the hostname to nginx on the host, and:

  • set the X-Forwarded-Proto header to https;
  • allow WebSocket upgrades;
  • keep everything on one hostname. Sign-in cookies only work when the application and its API share an address;
  • rate limit /api if you want protection against password guessing. Isoflow does not limit requests itself.

Enabling TLS in nginx

  1. Put your certificate chain and private key in a certs folder beside docker-compose.yml, as fullchain.pem and privkey.pem.
  2. In docker-compose.yml, uncomment the "443:443" port and the ./certs:/etc/nginx/certs:ro volume.
  3. In 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.
  4. Set APP_ORIGIN to your https:// address and run docker compose up -d.

Example: a Let's Encrypt certificate

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 reload

8. Sign in with Google (optional)

By default, users sign in with an email address and password. To add “Sign in with Google”:

  1. In the Google Cloud console, create an OAuth client of type Web application.
  2. Add one authorised redirect URI, exactly <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.
  3. If your organisation uses Google Workspace, set the OAuth consent screen's user type to Internal, so that only your organisation's accounts can sign in.
  4. Set 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.

9. Your data

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.

10. Backup and restore

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).dump

To 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 app

You can restore a dump taken on an older version into a newer one: Isoflow migrates the database forward when it starts.

11. Upgrading

  1. Choose the new release on the download page and put its version in the 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.
  2. If your last docker login has expired, run the new one the download page gives you.
  3. Pull the new image and restart:
    docker compose pull app
    docker compose up -d

Isoflow 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.

12. Hosts that cannot pull images

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.tar

Fully air-gapped installations are not supported: sign-in with Google needs outbound access, for example.

13. Uninstalling

docker compose down       # stops and removes the containers; keeps your database
docker compose down -v    # also deletes the database volume, permanently

Take a backup before docker compose down -v. It cannot be undone.

14. Your licence

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.

SituationWhat happens
Missing or invalid keyIsoflow does not start. The log says which problem it found: missing, damaged, or not issued for this version.
Expired, within the grace periodEverything 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 overIsoflow 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.

15. Logs and troubleshooting

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 healthy

Logs 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.

SymptomLikely 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 upThe 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 editedThe diagram workers failed to start. docker compose logs app shows the error; include it when you contact support.
nginx returns 502 Bad GatewayThe application is not running. Check docker compose logs app.
Google sign-in fails with redirect_uri_mismatchThe 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 inThe 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 changeAUTH_SECRET changed. Sessions signed with the old secret are no longer valid.

16. Known limitations

  • No AI icon generation.
  • No adding your own icons beyond the built-in icon set.
  • No real-time editing of one diagram by several people at once.
  • Sign-in by email and password, or Google, only: no Entra ID, Okta, Keycloak or SAML.
  • Configuration changes need a restart: every change to .env takes effect when the application container is recreated.

17. Support

When you contact support, include:

  • the boot report from docker compose logs app, which contains your licence id;
  • any errors from the logs around the time of the problem.

What support covers is set out in your agreement.

Isoflow

A Revarex tool.
Copyright © 2026 Revarex Limited.

Resources

Privacy policyTerms of serviceRefund policyCookie policy