For the complete documentation index, see llms.txt. This page is also available as Markdown.

Docker Deployment

Deploy Hexabot with Docker Compose, PostgreSQL, NGINX, and HTTPS.

Choose this method when you want a containerized deployment with Compose-managed services.

Use this path when you want Docker to manage the Hexabot app, PostgreSQL, or other supporting services.

For shared deployment concepts, see How can I deploy my Hexabot project?.

Prerequisites

You need:

  • A Linux server with SSH access

  • Node.js >= 24.17.0

  • One package manager: npm, pnpm, yarn, or bun

  • Docker Engine with the Compose plugin

  • NGINX

  • Certbot

  • PostgreSQL if you do not use the Compose PostgreSQL service

Helpful setup guides:

Install Docker

Install Docker Engine with the official guide for your operating system:

If you run Ubuntu, you can use this example:

sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker

After Docker is installed, allow your user to run Docker without sudo:

sudo usermod -aG docker $USER

Then log out and back in before you continue.

If you need to keep working in the current session, use this temporary workaround:

sudo chmod 666 /var/run/docker.sock

Without the group step, Hexabot CLI commands that call Docker can fail with a permission error.

Prepare the project

Install the CLI globally:

Clone the project and prepare the Docker env file:

Use these commands during setup and validation:

Docker Compose reads .env.docker.

Configure environment variables

Generate strong secrets before you edit .env.docker:

Update all public URL and origin variables before the first production start.

Do not leave the .env.docker localhost defaults in place.

Those values control CORS, cookies, and links sent in emails.

Update the production domain values together

When you change APP_DOMAIN, also change these values to the same public domain:

Variable
Production value

APP_DOMAIN

mychatbot.ai

API_ORIGIN

https://mychatbot.ai/api

FRONTEND_BASE_URL

https://mychatbot.ai

FRONTEND_ORIGIN

https://mychatbot.ai

HTTPS_ENABLED

true

If these values still point to http://localhost:*, production requests can fail, cookies may behave incorrectly, and email links can point to the wrong host.

Secrets

Replace every development placeholder in .env.docker before the first public start.

Change these values at minimum:

  • JWT_SECRET

  • PASSWORD_RESET_SECRET

  • CONFIRM_ACCOUNT_SECRET

  • SIGNED_URL_SECRET

  • SESSION_SECRET

  • DB_PASSWORD if PostgreSQL uses password authentication

This is the typical set of values that must not stay on template defaults such as dev_only.

Generate them all in one step:

Copy the output into .env.docker and replace the existing values.

Set these core values:

Variable
Example value
Notes

NODE_ENV

production

Use production for ongoing public deployment.

PORT

3000

Default Hexabot app port.

APP_DOMAIN

mychatbot.ai

Public domain name.

SSL_EMAIL

admin@mychatbot.ai

Email used by Certbot.

API_ORIGIN

https://mychatbot.ai/api

Public API base URL.

FRONTEND_BASE_URL

https://mychatbot.ai

Public app URL.

FRONTEND_ORIGIN

https://mychatbot.ai

Allowed browser origin.

HTTPS_ENABLED

true

Enable public HTTPS-aware behavior.

DB_TYPE

postgres

Recommended production database.

DB_SYNCHRONIZE

false

Set this explicitly to false before any production start.

DB_AUTO_MIGRATE

true

Enable on one primary API node.

API_IS_PRIMARY_NODE

true

Set true on the node that runs migrations.

SESSION_SECRET

...

Use a long random value.

JWT_SECRET

...

Use a long random value.

PASSWORD_RESET_SECRET

...

Use a long random value.

CONFIRM_ACCOUNT_SECRET

...

Use a long random value.

SIGNED_URL_SECRET

...

Use a long random value.

UPLOAD_DIR

/uploads

Keep a mounted upload path.

UPLOAD_MAX_SIZE_IN_BYTES

52428800

50 MB. Match the NGINX body size.

Database values depend on your PostgreSQL source:

  • If you use the Compose PostgreSQL service, keep the Docker PostgreSQL settings from the template or overlay.

  • If you use an external PostgreSQL server, set DB_HOST, DB_PORT, DB_USERNAME, DB_PASSWORD, DB_NAME, DB_SCHEMA, or DB_URL for that server.

Optional SMTP variables:

Variable
Example value
Notes

EMAIL_SMTP_ENABLED

true

Enable transactional email.

EMAIL_SMTP_HOST

smtp.example.com

SMTP host.

EMAIL_SMTP_PORT

587

Common STARTTLS port.

EMAIL_SMTP_SECURE

false

Use true for implicit TLS on 465.

EMAIL_SMTP_USER

smtp-user

SMTP username or API key user.

EMAIL_SMTP_PASS

smtp-password

SMTP password or API key.

EMAIL_SMTP_FROM

Hexabot <noreply@mychatbot.ai>

Sender address.

Hexabot reads SMTP settings at startup. Restart the API service after any SMTP change.

Run the first database bootstrap

Fresh PostgreSQL deployments need one private bootstrap before public traffic.

Hexabot seeds the first admin and default data only outside production mode.

Set these variables in .env.docker before the first private start:

Use the Docker development command for this bootstrap:

Then confirm the services are running:

Wait for the API to finish booting.

On a typical server, this usually takes less than a minute after the containers are up.

Keep following the API logs until you see:

If that line does not appear, keep the log stream open and check for migration, database, or env errors above it.

Then verify that the admin user was seeded:

The result should include the SEED_ADMIN_EMAIL value you set for bootstrap.

Before you expose the public domain, confirm that the seeded admin can sign in from a trusted network.

After the first bootstrap:

  1. Set NODE_ENV=production

  2. Set DB_SYNCHRONIZE=false in .env.docker

  3. Keep DB_AUTO_MIGRATE=true on one primary API node only

  4. Stop the bootstrap stack with hexabot docker down

  5. Continue with the production start below

Start the production stack

Start Hexabot with Docker:

For day-to-day operations, these commands are useful:

If hexabot docker logs api -f behaves unexpectedly in production, use Docker directly:

Use the API container name shown by docker ps.

This fallback is more predictable for live operations.

hexabot docker logs can resolve a different Compose scope than the one used to start the stack.

NGINX should proxy to the published Hexabot app port, usually 127.0.0.1:3000.

Configure NGINX

Install NGINX:

Create a dedicated site config:

This forwards both the admin UI and /api routes to the same Hexabot app.

Do not add a rewrite rule for /api.

Keep APP_DOMAIN, API_ORIGIN, FRONTEND_BASE_URL, FRONTEND_ORIGIN, and HTTPS_ENABLED aligned with the same public domain you set in server_name.

If you increase UPLOAD_MAX_SIZE_IN_BYTES, raise client_max_body_size to match.

Enable the site and remove the default site:

Validate and reload NGINX:

Enable HTTPS with Certbot

Install Certbot and the NGINX plugin:

Verify DNS before you request the certificate:

If the result does not match your server yet, wait for DNS propagation before you continue.

Request the certificate:

Then verify and reload:

If you also use www, include it in the Certbot command.

Confirm automatic renewal is active:

Verify the deployment

Run these checks:

Final browser check:

Troubleshooting

The Docker stack starts, but the app is unreachable

Confirm the stack is up:

Then confirm NGINX still points to the published host port, usually 127.0.0.1:3000.

The browser loads the UI, but API requests fail

Do not rewrite /api.

Hexabot already serves the API under /api from the same app.

The first admin account was not created

The first admin is seeded only outside production mode.

For Docker deployments, run the first private bootstrap with hexabot dev --docker --services postgres -d.

Keep SEED_ADMIN_* variables set and DB_SYNCHRONIZE=true.

Then switch back to NODE_ENV=production and DB_SYNCHRONIZE=false.

Uploads fail

Confirm the upload path is mounted correctly inside the container.

Then keep client_max_body_size aligned with UPLOAD_MAX_SIZE_IN_BYTES.

DNS or HTTPS does not work

Check DNS first:

Then recheck NGINX and Certbot:

Last updated

Was this helpful?