Deploy: Co-op Cloud

This guide walks you through deploying Llamenos as a Co-op Cloud recipe. Co-op Cloud uses Docker Swarm with Traefik for TLS termination and the abra CLI for standardized app management — ideal for tech co-ops and small hosting collectives.

The recipe is maintained in a standalone repository.

Prerequisites

  • A server with Docker Swarm initialized and Traefik running as the reverse proxy
  • The abra CLI installed on your local machine
  • A domain name with DNS pointing to your server’s IP
  • SSH access to the server

If you’re new to Co-op Cloud, follow the Co-op Cloud setup guide first.

Quick start

# Add your server (if not already added)
abra server add hotline.example.com

# Clone the recipe (abra looks for recipes in ~/.abra/recipes/)
git clone https://github.com/rhonda-rodododo/llamenos-template.git \
  ~/.abra/recipes/llamenos

# Create a new Llamenos app
abra app new llamenos --server hotline.example.com --domain hotline.example.com

# Generate all secrets
abra app secret generate -a hotline.example.com

# Deploy
abra app deploy hotline.example.com

Visit https://hotline.example.com and follow the setup wizard to create your admin account.

Core services

The recipe deploys six services:

ServiceImagePurpose
webnginx:1.27-alpineReverse proxy with Traefik labels
appghcr.io/rhonda-rodododo/llamenosBun application server
dbpostgres:17-alpinePostgreSQL database
rustfsghcr.io/rustfs/rustfsS3-compatible file storage
relaydockurr/strfryNostr relay for real-time events
authentikghcr.io/goauthentik/serverIdentity provider (SSO, invite-based onboarding)

Secrets

All secrets are managed via Docker Swarm secrets (versioned, immutable):

SecretTypeDescription
hmac_secrethex (64 chars)HMAC signing key for session tokens
server_nostrhex (64 chars)Server Nostr identity key
db_passwordalnum (32 chars)PostgreSQL password
rustfs_accessalnum (20 chars)RustFS access key
rustfs_secretalnum (40 chars)RustFS secret key
authentik_secretalnum (50 chars)Authentik secret key

Generate all secrets at once:

abra app secret generate -a hotline.example.com

To rotate a specific secret:

# Bump the version in your app config
abra app config hotline.example.com
# Change SECRET_HMAC_SECRET_VERSION=v2

# Generate the new secret
abra app secret generate hotline.example.com hmac_secret

# Redeploy
abra app deploy hotline.example.com

Configuration

Edit the app configuration:

abra app config hotline.example.com

Key settings:

DOMAIN=hotline.example.com
LETS_ENCRYPT_ENV=production

# Display name shown in the app
HOTLINE_NAME=Hotline

# Telephony provider (configure after setup wizard)
# TWILIO_ACCOUNT_SID=
# TWILIO_AUTH_TOKEN=
# TWILIO_PHONE_NUMBER=

First login

After deployment, open your domain in a browser. You’ll be redirected to Authentik to create your admin account via invite link, then complete the setup wizard:

  1. Name your hotline — set the display name
  2. Choose channels — enable Voice, SMS, WhatsApp, Signal, and/or Reports
  3. Configure providers — enter credentials for each channel
  4. Review and finish

Configure webhooks

Point your telephony provider’s webhooks to your domain:

  • Voice (incoming): https://hotline.example.com/api/telephony/incoming
  • Voice (status): https://hotline.example.com/api/telephony/status
  • SMS: https://hotline.example.com/api/messaging/sms/webhook
  • WhatsApp: https://hotline.example.com/api/messaging/whatsapp/webhook
  • Signal: Configure bridge to forward to https://hotline.example.com/api/messaging/signal/webhook

See provider-specific guides: Twilio, SignalWire, Vonage, Plivo, Asterisk.

Optional: Enable transcription

Add the transcription overlay to your app config:

abra app config hotline.example.com

Set:

COMPOSE_FILE=compose.yml:compose.transcription.yml
WHISPER_MODEL=Systran/faster-whisper-base
WHISPER_DEVICE=cpu

Then redeploy:

abra app deploy hotline.example.com

The Whisper service requires 4 GB+ RAM. Use WHISPER_DEVICE=cuda if you have a GPU.

Optional: Enable Asterisk

For self-hosted SIP telephony (see Asterisk setup):

abra app config hotline.example.com

Set:

COMPOSE_FILE=compose.yml:compose.asterisk.yml
SECRET_ARI_PASSWORD_VERSION=v1
SECRET_BRIDGE_SECRET_VERSION=v1

Generate the additional secrets and redeploy:

abra app secret generate hotline.example.com ari_password bridge_secret
abra app deploy hotline.example.com

Optional: Enable Signal

For Signal messaging (see Signal setup):

abra app config hotline.example.com

Set:

COMPOSE_FILE=compose.yml:compose.signal.yml

Then redeploy:

abra app deploy hotline.example.com

Updating

abra app upgrade hotline.example.com

This pulls the latest recipe version and redeploys. Data is persisted in Docker volumes and survives upgrades.

Backups

Backupbot integration

The recipe includes backupbot labels for automated PostgreSQL and RustFS backups. If your server runs backupbot, backups happen automatically.

Manual backup

Use the included backup script:

# From the recipe directory
./pg_backup.sh <stack-name>
./pg_backup.sh <stack-name> /backups    # custom directory, 7-day retention

Or back up directly:

# PostgreSQL
docker exec $(docker ps -q -f name=<stack-name>_db) pg_dump -U llamenos llamenos | gzip > backup.sql.gz

# RustFS
docker run --rm -v <stack-name>_rustfs-data:/data -v /backups:/backups alpine tar czf /backups/rustfs-$(date +%Y%m%d).tar.gz /data

Monitoring

Health checks

All services have Docker health checks. Check status:

abra app ps hotline.example.com

The app exposes /api/health:

curl https://hotline.example.com/api/health
# {"status":"ok"}

Logs

# All services
abra app logs hotline.example.com

# Specific service
abra app logs hotline.example.com app

# Follow logs
abra app logs -f hotline.example.com app

Troubleshooting

App won’t start

abra app logs hotline.example.com app
abra app ps hotline.example.com

Check that all secrets are generated:

abra app secret ls hotline.example.com

Certificate issues

Traefik handles TLS. Check Traefik logs on your server:

docker service logs traefik

Ensure your domain’s DNS resolves to the server and ports 80/443 are open.

Secret rotation

If a secret is compromised:

  1. Bump the version in app config (e.g., SECRET_HMAC_SECRET_VERSION=v2)
  2. Generate the new secret: abra app secret generate hotline.example.com hmac_secret
  3. Redeploy: abra app deploy hotline.example.com

Service architecture

flowchart TD
    Internet -->|":443"| Traefik["Traefik<br/>(TLS termination)"]
    Traefik -->|":80"| Nginx["Nginx<br/>(reverse proxy)"]
    Nginx -->|":3000"| App["App<br/>(Bun)"]
    Nginx -->|"/nostr"| Relay["strfry<br/>(Nostr relay)"]
    App --> PostgreSQL[("PostgreSQL<br/>:5432")]
    App --> RustFS[("RustFS<br/>:9000")]
    App --> Authentik["Authentik<br/>(IdP)"]
    App -.->|"optional"| Whisper["Whisper<br/>:8080"]

Next steps