Skip to content

Install Buuck on a VPS

Buuck runs on your own server. This page tells you how to install Buuck on a VPS.

Do the steps in the given sequence. The installation needs about 30 minutes.

Buuck has three applications and three support services. Docker Compose starts all of them together.

Service Function Port
booking The public page. Your customers make their bookings here. 3002
dashboard The private page. Your staff manage the bookings here. 3001
server The API. The two applications get their data from the API. 3000
postgres The database. The database keeps all of your data. 5432
cache The cache. The cache makes the API faster. 6379
caddy The reverse proxy. It also gets the TLS certificates. 80, 443

Only the reverse proxy is open to the internet. The other services are on a private Docker network.

You need these items:

  • A VPS with 2 CPU cores, 4 GB of RAM and 20 GB of free disk space.
  • A 64-bit x86 processor. The Buuck images do not run on an ARM processor.
  • A Linux operating system. We test Buuck on Ubuntu 24.04.
  • Root access to the VPS, or a user account with sudo rights.
  • A domain name. You must be able to add records to its DNS zone.
  • The registry user name and your license key. We send them to you in an email.
  • The version number of your Buuck release. The same email gives you this number.
  • An SMTP account. Buuck sends the booking emails with this account.

This page uses example.com for the domain name. Replace example.com with your own domain name.

Buuck uses three subdomains. Add one DNS A record for each subdomain. Each record points to the IP address of your VPS.

Record Example Service
api api.example.com the API
admin admin.example.com the dashboard
book book.example.com the booking page

If your DNS provider has a proxy function, switch the proxy off. The reverse proxy on your VPS must get the requests directly.

Check one record before you continue:

Terminal window
dig +short api.example.com

The command shows the IP address of your VPS. If the command shows no address, wait 10 minutes. Then do the check again.

Connect to your VPS:

Terminal window
ssh root@YOUR-SERVER-IP

Install Docker Engine and the Docker Compose plugin:

Terminal window
curl -fsSL https://get.docker.com | sh

Check the two versions:

Terminal window
docker --version
docker compose version

Docker Compose must be version 2.20 or higher.

Open the necessary ports in the firewall. Ubuntu uses ufw:

Terminal window
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

Your Buuck images are in a private registry. Log in with the user name and the license key from your email.

Terminal window
echo 'YOUR-LICENSE-KEY' | docker login registry.buuck.io -u 'robot$YOUR-LICENSE-ID' --password-stdin

Docker shows the message Login Succeeded. Docker then keeps the license key on the VPS. You do this step one time only.

Make a folder for your configuration files:

Terminal window
mkdir -p /opt/buuck
cd /opt/buuck

Do all of the next steps in this folder.

Make a file with the name compose.yaml. Put this text in the file:

name: buuck
services:
postgres:
image: postgres:16-alpine
container_name: buuck-postgres
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER}']
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
networks:
- buuck
cache:
image: valkey/valkey:9.0.0-alpine3.22
container_name: buuck-cache
command:
- valkey-server
- --requirepass
- ${CACHE_PASSWORD}
- --dir
- /data
- --save
- '900 1'
- --maxmemory
- 256mb
- --maxmemory-policy
- allkeys-lru
environment:
CACHE_PASSWORD: ${CACHE_PASSWORD}
volumes:
- cache_data:/data
healthcheck:
test: ['CMD-SHELL', 'valkey-cli --no-auth-warning -a "$$CACHE_PASSWORD" ping | grep -q PONG']
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
networks:
- buuck
db-migrate:
image: ${BUUCK_REGISTRY:-registry.buuck.io}/buuck/db-migrate:${BUUCK_VERSION}
container_name: buuck-db-migrate
environment:
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
APP_DB_USER: ${APP_DB_USER}
APP_DB_PASSWORD: ${APP_DB_PASSWORD}
NOTIFICATION_WORKER_USER: ${NOTIFICATION_WORKER_USER}
NOTIFICATION_WORKER_PASSWORD: ${NOTIFICATION_WORKER_PASSWORD}
depends_on:
postgres:
condition: service_healthy
restart: 'no'
networks:
- buuck
server:
image: ${BUUCK_REGISTRY:-registry.buuck.io}/buuck/server:${BUUCK_VERSION}
container_name: buuck-server
environment:
NODE_ENV: production
DATABASE_URL: postgresql://${APP_DB_USER}:${APP_DB_PASSWORD}@postgres:5432/${POSTGRES_DB}
DATABASE_NOTIFICATION_WORKER_URL: postgresql://${NOTIFICATION_WORKER_USER}:${NOTIFICATION_WORKER_PASSWORD}@postgres:5432/${POSTGRES_DB}
CACHE_URL: valkey://:${CACHE_PASSWORD}@cache:6379
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
BETTER_AUTH_URL: https://${API_DOMAIN}
CORS_ORIGIN: https://${DASHBOARD_DOMAIN},https://${BOOKING_DOMAIN}
EMAIL_PROVIDER: ${EMAIL_PROVIDER}
EMAIL_FROM: ${EMAIL_FROM}
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT}
SMTP_USER: ${SMTP_USER}
SMTP_PASS: ${SMTP_PASS}
SMTP_SECURE: ${SMTP_SECURE}
NOTIFICATION_POLL_INTERVAL_MS: ${NOTIFICATION_POLL_INTERVAL_MS}
depends_on:
postgres:
condition: service_healthy
cache:
condition: service_healthy
db-migrate:
condition: service_completed_successfully
restart: unless-stopped
networks:
- buuck
dashboard:
image: ${BUUCK_REGISTRY:-registry.buuck.io}/buuck/dashboard:${BUUCK_VERSION}
container_name: buuck-dashboard
environment:
NODE_ENV: production
ORIGIN: https://${DASHBOARD_DOMAIN}
PUBLIC_SERVER_URL: https://${API_DOMAIN}
depends_on:
server:
condition: service_healthy
restart: unless-stopped
networks:
- buuck
booking:
image: ${BUUCK_REGISTRY:-registry.buuck.io}/buuck/booking:${BUUCK_VERSION}
container_name: buuck-booking
environment:
NODE_ENV: production
ORIGIN: https://${BOOKING_DOMAIN}
PUBLIC_SERVER_URL: https://${API_DOMAIN}
depends_on:
server:
condition: service_healthy
restart: unless-stopped
networks:
- buuck
caddy:
image: caddy:2-alpine
container_name: buuck-caddy
environment:
ACME_EMAIL: ${ACME_EMAIL}
API_DOMAIN: ${API_DOMAIN}
DASHBOARD_DOMAIN: ${DASHBOARD_DOMAIN}
BOOKING_DOMAIN: ${BOOKING_DOMAIN}
ports:
- '80:80'
- '443:443'
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- server
- dashboard
- booking
restart: unless-stopped
networks:
- buuck
volumes:
postgres_data:
driver: local
cache_data:
driver: local
caddy_data:
driver: local
caddy_config:
driver: local
networks:
buuck:
driver: bridge

The reverse proxy sends each request to the correct service. It also gets a TLS certificate for each subdomain.

Make a file with the name Caddyfile. Put this text in the file:

{
email {$ACME_EMAIL}
}
{$API_DOMAIN} {
reverse_proxy server:3000
}
{$DASHBOARD_DOMAIN} {
reverse_proxy dashboard:3001
}
{$BOOKING_DOMAIN} {
reverse_proxy booking:3002
}

The environment file holds your domain names, your passwords and your secret key.

First make five random values. Run this command five times:

Terminal window
openssl rand -hex 24

Keep the five results. You use one result for each password and for the secret key.

Make a file with the name .env. Put this text in the file. Then replace each example value with your own value:

Terminal window
# Version
BUUCK_VERSION=v0.0.1
BUUCK_REGISTRY=registry.buuck.io
# Domain names
API_DOMAIN=api.example.com
DASHBOARD_DOMAIN=admin.example.com
BOOKING_DOMAIN=book.example.com
# Database
POSTGRES_DB=buuck
POSTGRES_USER=postgres
POSTGRES_PASSWORD=REPLACE-WITH-RANDOM-VALUE-1
APP_DB_USER=app_user
APP_DB_PASSWORD=REPLACE-WITH-RANDOM-VALUE-2
NOTIFICATION_WORKER_USER=notification_worker_user
NOTIFICATION_WORKER_PASSWORD=REPLACE-WITH-RANDOM-VALUE-3
# Cache
CACHE_PASSWORD=REPLACE-WITH-RANDOM-VALUE-4
# Authentication
BETTER_AUTH_SECRET=REPLACE-WITH-RANDOM-VALUE-5
# Email
EMAIL_PROVIDER=smtp
EMAIL_FROM="Your Business <[email protected]>"
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=
SMTP_PASS=
SMTP_SECURE=false
# Notifications
NOTIFICATION_POLL_INTERVAL_MS=60000

These are the most important variables:

Variable Function
BUUCK_VERSION The version of Buuck. Your email gives you this value.
BUUCK_REGISTRY The Docker registry host. Default: registry.buuck.io.
API_DOMAIN The address of the API.
DASHBOARD_DOMAIN The address of the dashboard.
BOOKING_DOMAIN The address of the booking page.
ACME_EMAIL Your email address. The certificate authority sends warnings to it.
BETTER_AUTH_SECRET The secret key for the user sessions. Do not change it after you start.
EMAIL_PROVIDER The type of email account. Use smtp, sendgrid, postmark or resend.
EMAIL_FROM The sender address of the booking emails.

Give the file the correct permissions. Then only the owner can read your passwords:

Terminal window
chmod 600 .env

Get the images from the registry:

Terminal window
docker compose pull

Start all of the services:

Terminal window
docker compose up -d

Docker starts the services in the correct sequence:

  1. The database and the cache start.
  2. The db-migrate service makes the database users and the database tables. Then this service stops.
  3. The API starts.
  4. The dashboard, the booking page and the reverse proxy start.

The first start needs about 2 minutes. The reverse proxy also gets the TLS certificates in this time.

Look at the status of the services:

Terminal window
docker compose ps

The db-migrate service shows the status exited (0). This status is correct. The service runs one time only. All of the other services show the status running or healthy.

Send a request to the API:

Terminal window
curl -i https://api.example.com

The API answers with 200 OK and the text OK.

Then open these two addresses in a browser:

  • https://admin.example.com shows the login page of the dashboard.
  • https://book.example.com shows the booking page.

The browser must show a valid certificate for each address. If a page does not open, read the Problems section.

  1. Open https://admin.example.com in a browser.
  2. Select Sign up.
  3. Give your name, your email address and a password.
  4. Select New organization.
  5. Give the name of your business and a short name for the address.

The dashboard opens. You can now add your venues, your resources and your offerings.

Your customers make their bookings at https://book.example.com/YOUR-SHORT-NAME.

Buuck keeps all of your data in the database. Make a backup of the database each day.

Write the backup to a file:

Terminal window
cd /opt/buuck
docker compose exec -T postgres sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > buuck-$(date +%F).sql

Also make a copy of your .env file. Keep both files on a different machine.

To put a backup back in the database, use this command:

Terminal window
docker compose exec -T postgres sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"' < buuck-2026-01-31.sql

Your license includes all later versions. We send you an email for each new version.

  1. Open the .env file.
  2. Change the value of BUUCK_VERSION to the new version.
  3. Get the new images:
Terminal window
docker compose pull
  1. Start the new version:
Terminal window
docker compose up -d

Docker replaces only the services with a new image. The database keeps your data.

Stop all of the services:

Terminal window
cd /opt/buuck
docker compose stop

Start all of the services again:

Terminal window
docker compose start

To remove the containers, use this command:

Terminal window
cd /opt/buuck
docker compose down
Terminal window
docker compose down --volumes

Read the messages of a service. Replace server with the name of the service:

Terminal window
cd /opt/buuck
docker compose logs -f server
Problem Cause and correction
Docker shows unauthorized or denied for a pull. The login is not valid. Do Step 3 again.
Docker shows manifest unknown for a pull. The value of BUUCK_VERSION is not correct. Compare the value with your email.
Docker shows exec format error for a service. The processor of your VPS is not a 64-bit x86 processor. Use a different VPS.
The browser shows a certificate error. The DNS records are not correct, or a proxy is active. Also make sure that port 80 is open.
The db-migrate service shows exited (1). Read the messages of the service. Then start it again with docker compose up -d db-migrate.
The dashboard shows a connection error. The value of API_DOMAIN or DASHBOARD_DOMAIN is not correct. Correct the value. Then run docker compose up -d --force-recreate.
The emails do not arrive. The SMTP values are not correct. Read the messages of the server service.
A service starts again and again. The .env file has an empty value. Compare the file with the example in Step 7.

If a problem continues, answer the email that we sent to you after you bought Buuck. Add the messages of the services to your answer.