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
abraCLI 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:
| Service | Image | Purpose |
|---|---|---|
| web | nginx:1.27-alpine | Reverse proxy with Traefik labels |
| app | ghcr.io/rhonda-rodododo/llamenos | Bun application server |
| db | postgres:17-alpine | PostgreSQL database |
| rustfs | ghcr.io/rustfs/rustfs | S3-compatible file storage |
| relay | dockurr/strfry | Nostr relay for real-time events |
| authentik | ghcr.io/goauthentik/server | Identity provider (SSO, invite-based onboarding) |
Secrets
All secrets are managed via Docker Swarm secrets (versioned, immutable):
| Secret | Type | Description |
|---|---|---|
hmac_secret | hex (64 chars) | HMAC signing key for session tokens |
server_nostr | hex (64 chars) | Server Nostr identity key |
db_password | alnum (32 chars) | PostgreSQL password |
rustfs_access | alnum (20 chars) | RustFS access key |
rustfs_secret | alnum (40 chars) | RustFS secret key |
authentik_secret | alnum (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:
- Name your hotline — set the display name
- Choose channels — enable Voice, SMS, WhatsApp, Signal, and/or Reports
- Configure providers — enter credentials for each channel
- 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:
- Bump the version in app config (e.g.,
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
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
- Admin Guide — configure the hotline
- Self-Hosting Overview — compare deployment options
- Docker Compose deployment — alternative single-server deployment
- Recipe repository — Co-op Cloud recipe source
- Co-op Cloud documentation — learn more about the platform