Skip to content

Single-Host Deployment (Ansible)

Deploy the full Sibyl stack to one Linux host with the bundled Ansible role. Suited to a personal instance on a small cloud VM: one box, no Kubernetes, modest cost.

Architecture

Four containers, managed by a sibyl.service systemd unit:

ContainerSourcePurpose
surrealdbsurrealdb/surrealdb:v3.1.0Graph, content, and auth store
backendghcr.io/hyperb1iss/sibyl-apiFastAPI + MCP server
frontendghcr.io/hyperb1iss/sibyl-webNext.js web UI
caddybuilt from caddy:2 with the Cloudflare DNS moduleTLS + path routing

Caddy obtains a Let's Encrypt certificate over the Cloudflare DNS-01 challenge, so the host needs no inbound HTTP or HTTPS from the public internet. Pair it with a private network (Tailscale, WireGuard) and the instance stays unreachable except to you, while still serving a browser-trusted certificate on a real domain.

The backend and frontend images are pulled pre-built from the registry, so nothing heavy compiles on the host.

The sibyl role

infra/ansible/roles/sibyl/ provisions a host end to end:

  • installs Docker Engine and the Compose plugin
  • installs ufw: default-deny inbound, SSH allowed, HTTP and HTTPS reachable only on the proxy interface
  • deploys the compose stack and a rendered .env
  • runs the stack through a sibyl.service systemd unit

Variables

VariableDefaultPurpose
sibyl_domainsibyl.hyperbliss.techHostname Caddy serves
sibyl_version1.0.0-rc.1ghcr.io image tag
sibyl_dir/opt/sibylDeployment directory
sibyl_proxy_interfacetailscale0Interface HTTP/HTTPS is exposed on
sibyl_mcp_auth_modeonMCP bearer-token enforcement

Secrets have no defaults and must be supplied, ideally through ansible-vault: sibyl_jwt_secret, sibyl_surreal_password, sibyl_openai_api_key, sibyl_anthropic_api_key, sibyl_cf_api_token. The role asserts each one is set before doing any work.

Deploying

The role is provider-agnostic: any Ubuntu host reachable over SSH works.

Prerequisites

The role is not published to Ansible Galaxy, so two things must be in place before the playbook runs end to end:

  • roles_path — your playbook has to resolve the sibyl role from a checkout of this repository. Point ansible.cfg at it:

    ini
    [defaults]
    roles_path = roles:/path/to/sibyl/infra/ansible/roles
  • Secrets — the five vault variables listed under Variables have no defaults. The role asserts each one before it touches the host, so an unset secret aborts the run cleanly instead of leaving a half-built stack.

1. Inventory

Add the host to an inventory group:

yaml
cloud:
  hosts:
    my-host:
      ansible_host: 203.0.113.10
      ansible_user: deploy

2. Bootstrap (once)

A fresh cloud host usually permits only root. Run a one-time play that creates your admin user and authorizes its SSH key, so later runs connect as that user and host hardening can disable root SSH.

3. Provision

Apply the role, alongside a base-hardening role and optionally a Tailscale role, from your playbook:

bash
ansible-playbook site.yml --limit my-host

The role is idempotent; re-run it to roll out config or image changes.

Verifying

bash
# on the host
systemctl status sibyl
docker compose -f /opt/sibyl/docker-compose.yml ps
curl -sf https://<sibyl_domain>/api/health

Operations

  • Logs: docker compose -f /opt/sibyl/docker-compose.yml logs -f
  • Update: bump sibyl_version and re-run the role
  • Backups: schedule surreal export against the running stack and copy the dump off-host. The graph is your memory; back it up.

Reference

This project is deployed from a separate homelab Ansible repository that supplies the inventory, the bootstrap play, ansible-vault secrets, and a Tailscale role. It is a worked example of the layout above.

Released under the Apache-2.0 License.