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.
What you install
Section titled “What you install”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.
Before you start
Section titled “Before you start”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
sudorights. - 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.
Step 1: Add the DNS records
Section titled “Step 1: Add the DNS records”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:
dig +short api.example.comThe command shows the IP address of your VPS. If the command shows no address, wait 10 minutes. Then do the check again.
Step 2: Install Docker
Section titled “Step 2: Install Docker”Connect to your VPS:
ssh root@YOUR-SERVER-IPInstall Docker Engine and the Docker Compose plugin:
curl -fsSL https://get.docker.com | shCheck the two versions:
docker --versiondocker compose versionDocker Compose must be version 2.20 or higher.
Open the necessary ports in the firewall. Ubuntu uses ufw:
ufw allow 22/tcpufw allow 80/tcpufw allow 443/tcpufw enableStep 3: Log in to the Buuck registry
Section titled “Step 3: Log in to the Buuck registry”Your Buuck images are in a private registry. Log in with the user name and the license key from your email.
echo 'YOUR-LICENSE-KEY' | docker login registry.buuck.io -u 'robot$YOUR-LICENSE-ID' --password-stdinDocker shows the message Login Succeeded. Docker then keeps the license key on the VPS. You do this step one time only.
Step 4: Make the installation folder
Section titled “Step 4: Make the installation folder”Make a folder for your configuration files:
mkdir -p /opt/buuckcd /opt/buuckDo all of the next steps in this folder.
Step 5: Make the compose file
Section titled “Step 5: Make the compose file”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: bridgeStep 6: Make the reverse proxy file
Section titled “Step 6: Make the reverse proxy file”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}Step 7: Make the environment file
Section titled “Step 7: Make the environment file”The environment file holds your domain names, your passwords and your secret key.
First make five random values. Run this command five times:
openssl rand -hex 24Keep 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:
# VersionBUUCK_VERSION=v0.0.1BUUCK_REGISTRY=registry.buuck.io
# Domain namesAPI_DOMAIN=api.example.comDASHBOARD_DOMAIN=admin.example.comBOOKING_DOMAIN=book.example.com
# DatabasePOSTGRES_DB=buuckPOSTGRES_USER=postgresPOSTGRES_PASSWORD=REPLACE-WITH-RANDOM-VALUE-1APP_DB_USER=app_userAPP_DB_PASSWORD=REPLACE-WITH-RANDOM-VALUE-2NOTIFICATION_WORKER_USER=notification_worker_userNOTIFICATION_WORKER_PASSWORD=REPLACE-WITH-RANDOM-VALUE-3
# CacheCACHE_PASSWORD=REPLACE-WITH-RANDOM-VALUE-4
# AuthenticationBETTER_AUTH_SECRET=REPLACE-WITH-RANDOM-VALUE-5
# EmailEMAIL_PROVIDER=smtpSMTP_HOST=smtp.example.comSMTP_PORT=587SMTP_USER=SMTP_PASS=SMTP_SECURE=false
# NotificationsNOTIFICATION_POLL_INTERVAL_MS=60000These 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:
chmod 600 .envStep 8: Start Buuck
Section titled “Step 8: Start Buuck”Get the images from the registry:
docker compose pullStart all of the services:
docker compose up -dDocker starts the services in the correct sequence:
- The database and the cache start.
- The
db-migrateservice makes the database users and the database tables. Then this service stops. - The API starts.
- 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:
docker compose psThe 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.
Step 9: Check the installation
Section titled “Step 9: Check the installation”Send a request to the API:
curl -i https://api.example.comThe API answers with 200 OK and the text OK.
Then open these two addresses in a browser:
https://admin.example.comshows the login page of the dashboard.https://book.example.comshows the booking page.
The browser must show a valid certificate for each address. If a page does not open, read the Problems section.
Step 10: Make the first account
Section titled “Step 10: Make the first account”- Open
https://admin.example.comin a browser. - Select Sign up.
- Give your name, your email address and a password.
- Select New organization.
- 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.
Make a backup
Section titled “Make a backup”Buuck keeps all of your data in the database. Make a backup of the database each day.
Write the backup to a file:
cd /opt/buuckdocker compose exec -T postgres sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > buuck-$(date +%F).sqlAlso 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:
docker compose exec -T postgres sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"' < buuck-2026-01-31.sqlInstall an update
Section titled “Install an update”Your license includes all later versions. We send you an email for each new version.
- Open the
.envfile. - Change the value of
BUUCK_VERSIONto the new version. - Get the new images:
docker compose pull- Start the new version:
docker compose up -dDocker replaces only the services with a new image. The database keeps your data.
Stop and start Buuck
Section titled “Stop and start Buuck”Stop all of the services:
cd /opt/buuckdocker compose stopStart all of the services again:
docker compose startRemove Buuck
Section titled “Remove Buuck”To remove the containers, use this command:
cd /opt/buuckdocker compose downdocker compose down --volumesProblems
Section titled “Problems”Read the messages of a service. Replace server with the name of the service:
cd /opt/buuckdocker 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.