Skip to content

Deployment guide

TwistCue targets a single-box deployment for v2 (both API and admin on the same host as meacrypto, using systemd + Apache + Let’s Encrypt). Multi-node horizontal scaling is phase 2.

RoleHostname (planned)Purpose
App boxYOUR_APP_HOSTAPI + admin + crons
DBYOUR_DB_HOSTPostgres 15 (managed)
RedisYOUR_REDIS_HOSTCache / rate-limit
DNSRegistrartwistcue.com and admin.twistcue.com
DomainPoints toNotes
admin.twistcue.comYOUR_SERVER_IP (app box)The admin UI
api.twistcue.connectorme.comYOUR_SERVER_IPThe API
docs.twistcue.com (planned)Static CDNThis docs site

Two units on the app box:

/etc/systemd/system/twistcue-api.service:

[Unit]
Description=TwistCue API
After=network.target postgresql.service redis.service
[Service]
Type=simple
User=twistcue
WorkingDirectory=/opt/twistcue/app/services/api
EnvironmentFile=/etc/twistcue-api.env
ExecStart=/usr/bin/node dist/main.js
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target

/etc/systemd/system/twistcue-admin.service:

[Unit]
Description=TwistCue Admin (Next.js)
After=network.target twistcue-api.service
[Service]
Type=simple
User=twistcue
WorkingDirectory=/opt/twistcue/app/apps/admin
EnvironmentFile=/etc/twistcue-admin.env
ExecStart=/usr/bin/node .next/standalone/server.js
Environment=PORT=3100 HOSTNAME=127.0.0.1
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target

/etc/twistcue-api.env:

NODE_ENV=production
DATABASE_URL=postgres://twistcue:REDACTED@YOUR_DB_HOST:5432/twistcue
REDIS_URL=redis://YOUR_REDIS_HOST:6379
JWT_SECRET=REDACTED_LONG_RANDOM_STRING
ADMIN_JWT_SECRET=REDACTED
CF_STREAM_ACCOUNT_ID=REDACTED
CF_STREAM_API_TOKEN=REDACTED
CF_STREAM_WEBHOOK_SECRET=REDACTED
# ...RevenueCat, dLocal, pawaPay, card MID, etc.

/etc/twistcue-admin.env:

NODE_ENV=production
NEXT_PUBLIC_API_BASE=https://api.twistcue.connectorme.com/v1
ADMIN_INTERNAL_API_BASE=http://127.0.0.1:3000/v1

Both files must be chown twistcue:twistcue and chmod 0600.

Admin vhost (basic-auth guard until WebAuthn ships):

<VirtualHost *:443>
ServerName admin.twistcue.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/admin.twistcue.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/admin.twistcue.com/privkey.pem
# Optional: IP allowlist for pre-launch
# <RequireAll>
# Require ip YOUR_OFFICE_IP_CIDR
# </RequireAll>
# Pre-launch: basic-auth layer above the app login
# <Location />
# AuthType Basic
# AuthName "TwistCue Admin"
# AuthUserFile /etc/apache2/twistcue.htpasswd
# Require valid-user
# </Location>
ProxyPass / http://127.0.0.1:3100/
ProxyPassReverse / http://127.0.0.1:3100/
ProxyPreserveHost On
</VirtualHost>

API vhost:

<VirtualHost *:443>
ServerName api.twistcue.connectorme.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/api.twistcue.connectorme.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/api.twistcue.connectorme.com/privkey.pem
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
ProxyPreserveHost On
</VirtualHost>

From a checkout on the app box:

Terminal window
cd /opt/twistcue/app
git fetch --all && git checkout main && git pull
npm ci
cd services/api && npm run build && npx prisma migrate deploy
cd ../../apps/admin && npm run build
sudo systemctl restart twistcue-api twistcue-admin
  • v2 is single-box, so “zero-downtime” is best-effort. Restarts drop ~2s of active requests.
  • If a migration is destructive, run it during a maintenance window.
  • For real HA later: front with an LB, run 2+ nodes, sticky-none, rolling restart.

The docs app builds to static:

Terminal window
cd apps/docs
npm run build # outputs dist/ with Pagefind index

Serve dist/ from any CDN or static host (Cloudflare Pages, Netlify, S3+CloudFront, or nginx on the app box). No runtime needed.

Daily Postgres dumps to off-box storage (managed provider handles this if using a managed DB). Retain 30 days minimum. Test restore quarterly.

  • ❌ Commit any .env file.
  • ❌ Log JWTs or webhook payloads with PII.
  • ❌ Run prisma migrate reset in prod.
  • ❌ Expose port 3000 (API) or 3100 (admin) to the internet directly; always go through Apache with TLS.