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 five services:

ServiceImagePurpose
webnginx:1.27-alpineReverse proxy with Traefik labels
appghcr.io/rhonda-rodododo/llamenos-platformBun application server
dbpostgres:17-alpinePostgreSQL database
miniominio/minioS3-compatible file storage
relaydockurr/strfryNostr relay for real-time events

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
minio_accessalnum (20 chars)MinIO access key
minio_secretalnum (40 chars)MinIO secret key

Generate all secrets at once:

abra app secret generate -a hotline.example.com

To rotate a specific secret:

# 1. Bump the version in your app config
abra app config hotline.example.com
# Change 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

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=My Hotline

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

# Or SignalWire
# PBX_TYPE=signalwire
# SIGNALWIRE_PROJECT_ID=
# SIGNALWIRE_AUTH_TOKEN=
# SIGNALWIRE_PHONE_NUMBER=
# SIGNALWIRE_SPACE_URL=

# Secret versioning (bump to rotate)
SECRET_HMAC_SECRET_VERSION=v1
SECRET_SERVER_NOSTR_VERSION=v1
SECRET_DB_PASSWORD_VERSION=v1
SECRET_MINIO_ACCESS_VERSION=v1
SECRET_MINIO_SECRET_VERSION=v1

First login

After deployment, open your domain in a browser and follow the setup wizard:

  1. Create your admin account — set a display name and your PIN
  2. Name your hotline — set the display name shown in the app
  3. Choose channels — enable Voice, SMS, WhatsApp, Signal, and/or Reports
  4. Configure providers — enter credentials for each enabled channel
  5. 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.

Optional: Enable Signal sidecar

For Signal messaging (see Signal setup):

abra app config hotline.example.com

Set:

COMPOSE_FILE=compose.yml:compose.signal.yml
SECRET_SIGNAL_NOTIFIER_TOKEN_VERSION=v1

Generate the additional secret and redeploy:

abra app secret generate hotline.example.com signal_notifier_token
abra app deploy hotline.example.com

Optional: Enable SIP bridge

For self-hosted SIP telephony via Asterisk, FreeSWITCH, or Kamailio:

abra app config hotline.example.com

Set:

COMPOSE_FILE=compose.yml:compose.telephony.yml
PBX_TYPE=asterisk
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 transcription

Add the transcription overlay (requires 4 GB+ RAM):

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

Use WHISPER_DEVICE=cuda if your server has a GPU.

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 MinIO 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-$(date +%Y%m%d).sql.gz

# MinIO (object storage)
docker run --rm \
  -v <stack-name>_minio-data:/data \
  -v /backups:/backups \
  alpine tar czf /backups/minio-$(date +%Y%m%d).tar.gz /data

Restore PostgreSQL:

gunzip -c backup-20260101.sql.gz | \
  docker exec -i $(docker ps -q -f name=<stack-name>_db) \
  psql -U llamenos llamenos

Monitoring

Health checks

All services have Docker health checks. Check status:

abra app ps hotline.example.com

The app exposes health endpoints:

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

Logs

# All services
abra app logs hotline.example.com

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

# Follow logs in real time
abra app logs -f hotline.example.com app

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

abra command reference

CommandDescription
abra app ps hotline.example.comShow running containers and health
abra app logs [-f] hotline.example.com [service]View (and follow) logs
abra app config hotline.example.comEdit app config (opens $EDITOR)
abra app secret ls hotline.example.comList secrets and their versions
abra app secret generate hotline.example.com [name]Generate one or all secrets
abra app deploy hotline.example.comDeploy (or redeploy) the app
abra app upgrade hotline.example.comPull latest recipe and redeploy
abra app undeploy hotline.example.comStop and remove the app (data preserved)
abra app run hotline.example.com app -- bun run ...Run a one-off command in the app container

Service architecture

Co-op Cloud Architecture

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

Missing secrets appear with an empty version. Generate them:

abra app secret generate 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.

Database connection errors

Check the app container can reach PostgreSQL:

abra app run hotline.example.com app -- \
  bun -e "const { sql } = await import('bun'); await sql\`SELECT 1\`; console.log('ok')"

Secret rotation

If a secret is compromised:

  1. Bump the version in app config: abra app config hotline.example.com (e.g., change 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

strfry not connecting

Real-time events require strfry. If you see WebSocket errors:

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

Verify the Nginx config routes /nostr to the relay container on port 7777.

Next steps