Skip to main content

Managed Deployment

Set your deployment values

Fill these once. The domains, paths, volume names, and commands below update automatically across this page.

Derived values

HTTP domain: acme.db.example.comgRPC domain: acme.grpc.example.comDeploy root: /opt/liorandb-acmeVolume: liorandb-acme-dataMetrics network: liorandb-acme_metrics-privatePublic IP: 203.0.113.10

These same values now flow through the examples and commands below.

The hostname pattern used throughout this page is:

  • acme.db.example.com
  • acme.grpc.example.com

What this deployment pattern is

This is the recommended shape when you want one managed LioranDB environment for one customer or tenant:

  • public HTTPS for the HTTP API
  • public TLS on :443 for gRPC
  • metrics reachable only inside Docker
  • persistent named-volume storage
  • bootstrap password loaded from a mounted file
  • Caddy handling all internet-facing TLS

The three template files

The managed deployment directory contains:

  • README.md
  • compose.example.yaml
  • Caddyfile.example

Each file has a different job.

Caddyfile.example

The routing shape for your deployment is:

acme.db.example.com {
reverse_proxy liorandb:27018
}

acme.grpc.example.com {
reverse_proxy h2c://liorandb:27019
}

What the HTTP block does

acme.db.example.com {
reverse_proxy liorandb:27018
}

This means:

  • Caddy listens for public requests sent to acme.db.example.com
  • Caddy terminates public TLS for that hostname
  • Caddy forwards internal traffic to liorandb:27018

This is the public HTTP entrypoint for:

  • /live
  • /ready
  • /v1
  • driver HTTP requests
  • admin HTTP APIs

What the gRPC block does

acme.grpc.example.com {
reverse_proxy h2c://liorandb:27019
}

This means:

  • Caddy listens for public gRPC traffic on acme.grpc.example.com
  • Caddy terminates public TLS on port 443
  • Caddy forwards internal traffic as cleartext HTTP/2 to liorandb:27019

h2c is important here because the upstream hop is private Docker-network traffic, not public TLS.

Why this split matters

This pattern gives you:

  • one public hostname for the HTTP-facing API
  • one public hostname for gRPC
  • one internal HTTP bind
  • one internal gRPC bind

That matches the intended driver discovery flow cleanly.

compose.example.yaml

The Compose template creates two services:

  • liorandb
  • caddy

The liorandb service

Core shape:

image: liorandb/liorandb:pre-alpha
container_name: liorandb-acme
restart: unless-stopped
cpus: 2.0
mem_limit: 3.5g

This gives you:

  • the published server image
  • a readable per-client container name
  • automatic restarts
  • basic CPU and memory constraints

Why there is no command: override

The managed template intentionally keeps the image CMD unchanged.

That means the image still boots with its baked-in server command and existing startup arguments. The deployment template only changes:

  • environment variables
  • reverse proxying
  • persistence
  • networking

Environment variables

This is the managed shape:

LIORANDB_TLS_MODE=insecure
LIORANDB_GRPC_BIND_ADDR=0.0.0.0:27019
LIORANDB_METRICS_ADDR=0.0.0.0:27201
LIORANDB_ADVERTISED_HTTP_ADDR=acme.db.example.com:443
LIORANDB_ADVERTISED_GRPC_ADDR=acme.grpc.example.com:443
LIORANDB_BOOTSTRAP_PASSWORD_FILE=/run/liorandb/bootstrap-password.txt

What each variable does

LIORANDB_TLS_MODE=insecure

  • public TLS is terminated by Caddy
  • the internal Docker hop remains plaintext
  • this is acceptable because that hop is private to Docker

LIORANDB_GRPC_BIND_ADDR=0.0.0.0:27019

  • LioranDB listens internally for gRPC on 27019

LIORANDB_METRICS_ADDR=0.0.0.0:27201

  • metrics are exposed internally on 27201
`LIORANDB_ADVERTISED_HTTP_ADDR=acme.db.example.com:443`
  • /v1 should advertise the public HTTP authority clients must use
`LIORANDB_ADVERTISED_GRPC_ADDR=acme.grpc.example.com:443`
  • /v1 should advertise the public gRPC authority clients must use

LIORANDB_BOOTSTRAP_PASSWORD_FILE=/run/liorandb/bootstrap-password.txt

  • the server reads the initial admin password from the mounted file

Why expose: is used instead of ports:

The managed template should look like this:

expose:
- "27018"
- "27019"
- "27201"

That makes the ports reachable to other containers, but not publicly published on the VPS host.

This is one of the most important security properties of the deployment.

Persistence

The data volume pattern should look like:

volumes:
- liorandb-acme-data:/var/lib/liorandb/data
- ./clients/acme/bootstrap-password.txt:/run/liorandb/bootstrap-password.txt:ro

This gives you:

  • persistent database data in a named Docker volume
  • a read-only mounted bootstrap password file

Networks

The network pattern should look like:

networks:
- public-edge
- metrics-private

And the network definitions:

networks:
public-edge:
metrics-private:
internal: true

That means:

  • public-edge is shared between Caddy and LioranDB
  • metrics-private is intentionally internal-only

The caddy service

The public host port mapping should stay:

ports:
- "80:80"
- "443:443"
- "443:443/udp"

That means only Caddy is exposed publicly.

You should not publish:

  • 27018
  • 27019
  • 27201

directly on the VPS.

Fresh Ubuntu VPS guide

This flow assumes:

  • today is Friday, August 14, 2026
  • Docker is already installed
  • Docker commands require sudo

1. Point DNS first

Create two A records pointing at your VPS public IP:

acme.db.example.com -> 203.0.113.10
acme.grpc.example.com -> 203.0.113.10

If you use Cloudflare, start with DNS only while validating the stack.

2. SSH into the VPS and prepare directories

Prepare deployment directories
sudo mkdir -p /opt/liorandb-acme
sudo mkdir -p /opt/liorandb-acme/clients/acme
cd /opt/liorandb-acme

3. Create the bootstrap password

Generate a strong password:

Generate bootstrap password
openssl rand -base64 32 | sudo tee /opt/liorandb-acme/clients/acme/bootstrap-password.txt

Lock it down:

Restrict bootstrap password file
sudo chmod 600 /opt/liorandb-acme/clients/acme/bootstrap-password.txt

View it once:

Read bootstrap password
sudo cat /opt/liorandb-acme/clients/acme/bootstrap-password.txt

Save that password somewhere secure. This file is expected by the deployment template and is mounted read-only into the container.

4. Create the Caddyfile

Edit Caddyfile
sudo nano /opt/liorandb-acme/Caddyfile.example

Paste:

acme.db.example.com {
reverse_proxy liorandb:27018
}

acme.grpc.example.com {
reverse_proxy h2c://liorandb:27019
}

Save with:

  • Ctrl+O
  • Enter
  • Ctrl+X

5. Create the Compose file

Edit Compose
sudo nano /opt/liorandb-acme/compose.yaml

Paste:

services:
liorandb:
image: liorandb/liorandb:pre-alpha
container_name: liorandb-acme
restart: unless-stopped
cpus: 2.0
mem_limit: 3.5g
environment:
LIORANDB_TLS_MODE: insecure
LIORANDB_GRPC_BIND_ADDR: 0.0.0.0:27019
LIORANDB_METRICS_ADDR: 0.0.0.0:27201
LIORANDB_ADVERTISED_HTTP_ADDR: acme.db.example.com:443
LIORANDB_ADVERTISED_GRPC_ADDR: acme.grpc.example.com:443
LIORANDB_BOOTSTRAP_PASSWORD_FILE: /run/liorandb/bootstrap-password.txt
expose:
- "27018"
- "27019"
- "27201"
volumes:
- liorandb-acme-data:/var/lib/liorandb/data
- ./clients/acme/bootstrap-password.txt:/run/liorandb/bootstrap-password.txt:ro
networks:
- public-edge
- metrics-private

caddy:
image: caddy:2
container_name: caddy-acme
restart: unless-stopped
depends_on:
- liorandb
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile.example:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
networks:
- public-edge

volumes:
liorandb-acme-data:
caddy-data:
caddy-config:

networks:
public-edge:
metrics-private:
internal: true

6. Validate Compose before starting anything

Validate Compose
cd /opt/liorandb-acme
sudo docker compose -f compose.yaml config

If that prints normalized configuration without errors, the file structure and YAML are valid.

7. Pull images

Pull images
sudo docker compose -f compose.yaml pull
sudo docker images

You should pull:

  • liorandb/liorandb:pre-alpha
  • caddy:2

8. Configure the firewall

Keep SSH open:

Allow SSH
sudo ufw allow OpenSSH

Allow only public web/TLS traffic:

Allow web and TLS only
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw enable
sudo ufw status

Do not open:

  • 27018
  • 27019
  • 27201

Those should remain Docker-private.

9. Start the deployment

Start the stack
cd /opt/liorandb-acme
sudo docker compose -f compose.yaml up -d
sudo docker compose -f compose.yaml ps

You want both containers to show as up:

  • liorandb-acme
  • caddy-acme

10. Inspect logs immediately

LioranDB logs:

LioranDB logs
sudo docker logs -f liorandb-acme

You should eventually see HTTP, gRPC, and metrics listeners.

Caddy logs:

Caddy logs
sudo docker logs -f caddy-acme

You want successful certificate acquisition for:

  • acme.db.example.com
  • acme.grpc.example.com

11. Check public listening ports

Check listening ports
sudo ss -tulpn

You should see public listeners roughly like:

0.0.0.0:80
0.0.0.0:443

You should not see:

0.0.0.0:27018
0.0.0.0:27019
0.0.0.0:27201

That is the key security check.

12. Test the public HTTP side

Test /live and /v1
curl -v https://acme.db.example.com/live
curl -v https://acme.db.example.com/v1

The important /v1 response should advertise:

{
"http_addr": "acme.db.example.com:443",
"grpc_addr": "acme.grpc.example.com:443"
}

:::danger Stop here if discovery is wrong If /v1 still reports 0.0.0.0:27018 or 0.0.0.0:27019, then the currently published liorandb/liorandb:pre-alpha image likely predates the advertised address changes. In that case, the template is right, but the image must be rebuilt and republished before this managed flow is safe to rely on. :::

13. Test private metrics

List Docker networks:

List Docker networks
sudo docker network ls

Compose will usually create something like:

liorandb-acme_metrics-private

Then test metrics from a temporary container:

Test private metrics
sudo docker run --rm \
--network liorandb-acme_metrics-private \
curlimages/curl \
http://liorandb:27201/metrics

You should get Prometheus-format output.

14. Build the final driver URI

Your managed connection string becomes:

liorandb://admin:<your-encoded-password>@acme.db.example.com:443/default

If the password contains characters like:

  • @
  • #
  • !
  • /
  • :
  • %

URL-encode it first.

Examples:

  • @ -> %40
  • ! -> %21
  • # -> %23

Expected discovery flow:

liorandb://admin:<your-encoded-password>@acme.db.example.com:443/default
-> HTTPS to acme.db.example.com:443
-> GET /v1
-> grpc_addr = acme.grpc.example.com:443
-> TLS gRPC through Caddy when needed

15. Verify persistence before calling it done

After inserting test data, restart the database container:

Restart the database
sudo docker restart liorandb-acme

Verify the data is still there.

Then test a fuller cycle:

Down and up without deleting the volume
cd /opt/liorandb-acme
sudo docker compose -f compose.yaml down
sudo docker volume ls | grep liorandb
sudo docker compose -f compose.yaml up -d

Your data should remain because the named volume is mounted at /var/lib/liorandb/data.

Do not run:

sudo docker compose down -v

unless you intentionally want to delete the database volume.

Updating LioranDB later

When a newer server image is published:

Update the database image
cd /opt/liorandb-acme
sudo docker compose pull liorandb
sudo docker compose up -d

The persistent data volume remains untouched.

Final topology

Internet
|
|-- https://acme.db.example.com
| |
| v
| Caddy
| |
| v
| liorandb:27018
|
'-- acme.grpc.example.com:443
|
v
Caddy
|
v
h2c://liorandb:27019

metrics :27201
locked inside Docker

data
v
liorandb-acme-data

Final preflight reminder

Before you even begin the VPS rollout, verify that the currently published liorandb/liorandb:pre-alpha image was built after the advertised HTTP and gRPC address changes landed. If not, the deployment files can be perfect and the older binary can still ignore the new environment variables.