Skip to main content
Run the full Observal stack on a single VM with Docker Compose. Best for teams of up to ~50 users, internal tools, evaluations, and any deployment where simplicity matters more than multi-AZ redundancy. End state: Observal running behind TLS on one server, with automated backups to S3-compatible storage, surviving reboots, and upgradable in under a minute.

When to use this vs. the Terraform module

Architecture

Everything runs as Docker containers on a single host. The nginx LB routes traffic and terminates TLS. Docker’s restart policy and systemd keep the stack running across reboots.

Prerequisites

Sizing guide

ClickHouse is the memory consumer. If you run out, increase CLICKHOUSE_MEMORY_LIMIT before resizing the VM.

Step 1: Provision the VM

Use any cloud provider or on-prem hypervisor. Example for AWS:
For other clouds:
  • GCP: e2-standard-2 with 100 GB balanced persistent disk
  • Azure: Standard_B2ms with 100 GB Premium SSD
  • Hetzner: CPX31 (4 vCPU / 8 GB, ~€15/mo)
  • On-prem: any Linux box meeting the specs above

Step 2: Install Docker

SSH into the VM and install Docker:

Step 3: Clone and configure

Edit .env for production. At minimum, change these:
Write them into .env:
SAML SSO, audit logs, and executive dashboards are included in the open-source distribution. See Configuration. For mounted credentials and private keys, use the documented NAME_FILE inputs instead of placing secret contents in .env.

Step 4: Set up TLS

This guide uses the source Compose stack. Every localhost:80 example below refers to that stack’s nginx load balancer. Do not expose its HTTP listener directly to remote clients. For downloadable server-package bindings, see Requirements.

Option A: Caddy (simplest — automatic HTTPS)

Create /etc/caddy/Caddyfile:
Caddy handles Let’s Encrypt certificates automatically. No renewal cron needed.

Option B: Certbot + nginx production config

Then use the production compose overlay which configures nginx for TLS:
Set up auto-renewal:

Option C: Behind a cloud load balancer

If your VM sits behind an AWS ALB, GCP HTTPS LB, or Cloudflare, terminate TLS there and proxy to port 80 on the VM. No TLS config on the VM itself.

Step 5: Start the stack

First build takes 3–5 minutes (pulling images, building the API and web containers). Watch the logs:
Wait for:
Verify:

Step 6: Survive reboots

Docker’s restart: unless-stopped policy (already set in docker-compose.yml) handles container restarts. To ensure Docker itself starts on boot:
Test it:

Step 7: Set up automated backups

Create a backup script:
Schedule it:
For ClickHouse (weekly, since it’s larger):
See Backup and restore for detailed restore procedures.

Step 8: First login

Install the CLI on your local machine:
Log in:
Verify:

Upgrades

The Compose init service applies migrations before API startup. See Upgrades for rollback procedures. Or use the CLI:

Monitoring

Prometheus and Grafana are optional. Start the core stack with Prometheus only:
Start Prometheus and Grafana:
Access Grafana at http://your-server:3001 when the Grafana profile is enabled. For basic alerting without Grafana, add a health check cron:

Security hardening

The security assurance case describes the deployment trust boundaries and residual risks. Before exposing to the internet:
  • Remove all DEMO_* env vars from .env
  • Set a strong SECRET_KEY
  • Restrict SSH access (key-only, no password auth)
  • Configure a firewall (ufw allow 80,443/tcp && ufw enable)
  • Set up SSO if available
  • Enable automatic security updates (sudo apt install unattended-upgrades)
  • Bind database ports to localhost only (already done in docker-compose.yml via 127.0.0.1: prefix)

Scaling up

When you outgrow a single node:

Next