The deployment/ sub-project contains Docker Compose definitions, reverse-proxy configs, Gatus monitoring, and migration bundles for running the Amanat escrow platform. It covers three distinct stacks: a legacy compose (reference only), the dev-amn active dev stack (dev.amn.gg), and the escrow-multi white-label stack (multi.amn.gg).
1. Overview
File
Status
Host
Notes
deployment/docker-compose.yml
Legacy / reference
Any
nginx + traefik_public network; images from git.manko.yoga registry. Do not deploy from this.
deployment/dev-amn/docker-compose.yml
Active
89.58.32.32
shared-web + infra-caddy ingress; images from git.tbs.amn.gg/escrow
The dev-amn stack is the authoritative dev deployment. The escrow-multi stack is the only valid target for feature/white-label-shops branch work.
[!warning] Branch / stack isolation
Work on feature/white-label-shops must NEVER touch escrow-dev — no restart, redeploy, or env change. Work on main must NEVER touch escrow-multi. Each stack must have its own TELEGRAM_BOT_TOKEN (different bots). See deploy_architecture_two_stacks.
2. Services
2.1 dev-amn stack (active, dev.amn.gg)
Service
Image
Internal Port
Role
backend
git.tbs.amn.gg/escrow/backend:dev
5001
Express 5 API + Socket.IO + admin seed
frontend
git.tbs.amn.gg/escrow/frontend:dev
8083
Next.js SSR app
refscanner
git.tbs.amn.gg/escrow/scanner:dev
8080
In-house AMN payment scanner (SQLite)
postgres
postgres:18-alpine
5432
Primary datastore (auth + all 8 Postgres domain stores)
redis
redis:8-alpine
6379
Session cache, rate-limit counters, pub/sub
mongodb
mongo:8.0-noble
27017
Legacy datastore — dev boot parity only; retire once remaining reads migrated
The Gatus config (deployment/gatus/config.yaml) is bind-mounted read-only into the gatus container at /config/config.yaml. It lives in the repo, not in ./data/.
Postgres volume note:postgres:18 uses a version-scoped data directory layout and refuses to init into a volume root that already contains files from a different layout. PGDATA is set to a subdirectory (/var/lib/postgresql/data/pgdata) inside the mount to avoid init conflicts.
Legacy compose bind mounts (/var/data/escrowDev/)
Service
Host Path
Container Path
nginx
/var/data/escrowDev/nginx/nginx.conf
/etc/nginx/nginx.conf (ro)
nginx
/var/data/escrowDev/nginx/logs
/var/log/nginx
nginx / backend
/var/data/escrowDev/uploads
/uploads / /app/uploads
mongodb
/var/data/escrowDev/mongodb_data
/data/db
mongodb
/var/data/escrowDev/mongo-init
/docker-entrypoint-initdb.d
postgres
/var/data/escrowDev/postgres_data
/var/lib/postgresql
redis
/var/data/escrowDev/redis_data
/data
6. Reverse Proxy (infra-caddy) Integration
Ingress for 89.58.32.32 is handled exclusively by infra-caddy — the Caddy container in the Arcane project infra. It owns host ports 80 and 443. No service should bind those ports directly.
Caddyfile block for dev.amn.gg
Live location on server: /opt/arcane/data/projects/infra/Caddyfile
Reference copy in repo: deployment/dev-amn/Caddyfile
Gatus runs as a sidecar in the default network. Config is at deployment/gatus/config.yaml (bind-mounted read-only). Alerts are delivered via Telegram to GATUS_TELEGRAM_CHAT_ID.
In the legacy compose, Gatus is exposed on host port 8084 (mapped from container :8080) and publicly accessible via Traefik at gatus.ch.manko.yoga.
The backend-dev-health endpoint validates all 8 domain stores running on Postgres: auth, config, address, category, levelConfig, shopSettings, review, notification. A failure here means a store mode regression or a broken PG_URL.
Gatus dashboard: :8084 on the host locally (not publicly proxied by default — access via SSH tunnel, or add a Caddyfile block if public exposure is needed).
8. Environment Variables
All vars are injected via .env at the stack root. The server file is chmod 600 and never committed. The deployment/.env in the repo serves as the live dev reference / template.
Each stack (dev, multi) must have a different TELEGRAM_BOT_TOKEN — sharing a bot token kills one stack's webhook when the other registers. See escrow_multi_woodpecker_deploy and stack isolation warning above.
8.23 Pangolin / Newt (optional VPN mesh)
Variable
Description
PANGOLIN_ENDPOINT
Pangolin tunnel endpoint
NEWT_ID
Newt node ID
NEWT_SECRET
Newt node secret
8.24 Frontend (NEXT_PUBLIC_*)
Variable
Description
NEXT_PUBLIC_API_URL
Backend API URL (browser-visible)
NEXT_PUBLIC_SOCKET_URL
Socket.IO server URL
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID
WalletConnect project ID
NEXT_PUBLIC_ALCHEMY_API_KEY_MAINNET
Alchemy mainnet key
NEXT_PUBLIC_ALCHEMY_API_KEY_SEPOLIA
Alchemy Sepolia key
NEXT_PUBLIC_ALCHEMY_API_KEY_POLYGON
Alchemy Polygon key
NEXT_PUBLIC_ESCROW_WALLET_ADDRESS
Escrow wallet (shown in UI)
NEXT_PUBLIC_APP_NAME
App display name
NEXT_PUBLIC_APP_VERSION
App version string
NEXT_PUBLIC_MAPBOX_API_KEY
Mapbox key (address autocomplete)
NEXT_PUBLIC_PASSKEY_RP_NAME
WebAuthn RP name
NEXT_PUBLIC_PASSKEY_RP_ID
WebAuthn RP ID
NEXT_PUBLIC_PASSKEY_ORIGIN
WebAuthn origin
NEXT_PUBLIC_BACKEND_URL
Backend origin (direct calls)
NEXT_PUBLIC_DEPAY_INTEGRATION_ID
DePay integration ID
NEXT_PUBLIC_IS_DEVELOPMENT
Development flag
NEXT_PUBLIC_ENABLE_DEBUG
Enable client debug logging
NEXT_PUBLIC_APP_URL
Canonical app URL
NEXT_PUBLIC_TELEGRAM_BOT_ID
Telegram bot numeric ID
BUILD_STATIC_EXPORT
Enable next export mode (false for SSR)
NEXT_PUBLIC_* vars are baked into the frontend bundle at build time. Never put secrets in them. Frontend env changes require a fresh image build and redeploy.
8.25 Gatus
Variable
Description
GATUS_TELEGRAM_BOT_TOKEN
Telegram bot for alert delivery
GATUS_TELEGRAM_CHAT_ID
Target chat ID for alerts
9. Deploy Workflow
9.1 Normal image update (CI-driven — dev-amn)
Woodpecker CI builds backend and frontend images, pushes to git.tbs.amn.gg/escrow/, then triggers an Arcane GitOps sync which pulls the new image and recreates the container.
git push origin dev
└─► Woodpecker build pipeline
└─► docker push git.tbs.amn.gg/escrow/backend:dev
└─► docker push git.tbs.amn.gg/escrow/frontend:dev
└─► arcane-cli gitops sync cf6c9eab… (or watchtower polls)
└─► escrow-backend restarted with new image
└─► escrow-frontend restarted with new image
Always use Woodpecker. Never use manual rsync/docker-build/ssh for escrow-multi.
# 1. Make changes, bump version in package.json# 2. Commit
git commit -m "fix: description (vX.Y.Z)"# 3. Push to Forgejo (remote is "forgejo", not "origin")
git push forgejo feature/white-label-shops
# 4. Monitor Woodpecker pipelinesource ~/CascadeProjects/escrow/.env
WOODPECKER_SERVER=$WOODPECKER_SERVERWOODPECKER_TOKEN=$WOODPECKER_TOKEN\
woodpecker-cli pipeline ls escrow/backend
Frontend is a separate Woodpecker project (escrow/frontend). Both push targets trigger their respective pipelines.
9.3 Manual hotfix deploy (backend only — no registry cycle)
For urgent fixes without a full CI cycle, build locally on the server:
# 1. Copy changed files to build tree
scp -i ~/CascadeProjects/wzp src/services/auth/authRoutes.ts \
root@89.58.32.32:/tmp/escrow-backend-build/src/services/auth/
# 2. Rebuild image on server (~3 min, ARM64)
ssh -i ~/CascadeProjects/wzp root@89.58.32.32 \
"cd /tmp/escrow-backend-build && docker build -f Dockerfile.prod \
-t escrow-backend-local:dev ."# 3. Restart the backend container
ssh -i ~/CascadeProjects/wzp root@89.58.32.32 \
"cd /opt/arcane/data/projects/escrow-dev && docker compose up -d backend"
The docker-compose.override.yml at /opt/arcane/data/projects/escrow-dev/docker-compose.override.yml sets pull_policy: never for escrow-backend-local:dev so watchtower never clobbers it.
CI ✓ green does NOT guarantee the new image was pushed. Always verify curl /api/version returns the expected version. See woodpecker_silent_build_fail.
10. Dev vs Prod Differences
Aspect
dev-amn (dev.amn.gg)
Prod (amn.gg)
Compose file
deployment/dev-amn/docker-compose.yml
Separate prod stack (not in this repo)
Image registry
git.tbs.amn.gg/escrow
Same registry, prod tags
Image tag
:dev
:latest or versioned
MongoDB
Present (dev parity — retired in prod)
Not present
ENABLE_TESTNET_CHAINS
true (compose override)
Not set / false
NODE_ENV
production
production
NEXT_PUBLIC_IS_DEVELOPMENT
false
false
PAYMENT_PROVIDER_MODE
live
live
REQUEST_NETWORK_ALLOW_TEST_WEBHOOKS
May be true for RN testing
false
TRANSACTION_SAFETY_MIN_CONFIRMATIONS
12 (default)
12 (default)
Gatus monitoring
Monitors both dev + prod endpoints
Shared Gatus instance
TLS
Cloudflare proxy → Caddy (disable_redirects)
Same
Version bump
Required before CI push
Required
Watchtower labels
Present in legacy compose
Prod stack may differ
11. Secret Management
The .env file on the server is the single source of runtime secrets. It is never committed.
Server location:/opt/arcane/data/projects/escrow-dev/.env
Permissions:chmod 600, owned by root
Repo template:deployment/.env — contains live dev values, treated as low-sensitivity dev config; rotate all values before using in production
Rules
Never commit .env or any file containing real tokens, passwords, or private keys.
Never pass secrets as Dockerfile ARG/ENV at build time — they appear in image layers. All secrets are runtime-injected via env_file.
NEXT_PUBLIC_* vars are baked into the frontend bundle. Never place secrets in them.
Wallet addresses are public on-chain but still kept out of the repo for operational hygiene.
For new deployments: copy deployment/.env to the server, fill in real values, chmod 600.
Gatus vars (GATUS_TELEGRAM_BOT_TOKEN, GATUS_TELEGRAM_CHAT_ID) go into the same .env.
Telegram bot tokens are high-value — rotate immediately if accidentally pushed.