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.0One package manager:
npm,pnpm,yarn, orbunDocker 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 dockerAfter Docker is installed, allow your user to run Docker without sudo:
sudo usermod -aG docker $USERThen 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.sockWithout 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:
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_SECRETPASSWORD_RESET_SECRETCONFIRM_ACCOUNT_SECRETSIGNED_URL_SECRETSESSION_SECRETDB_PASSWORDif 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.
Do not deploy with dev_only, change-me, or any checked-in example secret.
Set these core values:
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.
Set DB_SYNCHRONIZE=false explicitly in .env.docker before you start the production stack.
Do not leave it unset.
The production Compose file falls back to DB_SYNCHRONIZE=true when the variable is missing.
That can trigger TypeORM schema sync against a production database.
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, orDB_URLfor that server.
Optional SMTP variables:
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:
DB_SYNCHRONIZE=true is useful for the first bootstrap only.
After the first admin and schema exist, switch back to NODE_ENV=production and DB_SYNCHRONIZE=false.
Use the Docker development command for this bootstrap:
This bootstrap command can also start pgAdmin on port 9000.
On a public cloud server, that port may be reachable from the internet unless your firewall blocks it.
The dev pgAdmin service uses development credentials and is not meant for public exposure.
During bootstrap, restrict port 9000 with your firewall or trusted-network rules.
As soon as bootstrap is complete, stop the stack with hexabot docker down.
Do not use hexabot start --docker for the first bootstrap.
That command uses the production Compose stack.
The production Compose config sets NODE_ENV=production.
That overrides NODE_ENV=development from .env.docker.
In production mode, the admin seeder does not run.
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:
Set
NODE_ENV=productionSet
DB_SYNCHRONIZE=falsein.env.dockerKeep
DB_AUTO_MIGRATE=trueon one primary API node onlyStop the bootstrap stack with
hexabot docker downContinue with the production start below
Start the production stack
Before you run hexabot start --docker, recheck .env.docker.
Make sure NODE_ENV=production and DB_SYNCHRONIZE=false are both present.
If DB_SYNCHRONIZE is missing, the production Compose stack can default it to true.
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:
Related pages
Last updated
Was this helpful?