Complete step-by-step guide to go from zero to a live testnet deployment ready for SCF Build Award submission.
Total time: ~3 hours (mostly waiting for builds/deploys)
Step 1 Generate keypairs (5 min)
Step 2 Fund testnet accounts (5 min)
Step 3 Build + deploy contracts (20 min — mostly build time)
Step 4 Initialise contracts (5 min)
Step 5 Deploy backend to Railway (20 min)
Step 6 Run migrations (5 min)
Step 7 Deploy frontend to Vercel (10 min — already linked to GitHub)
Step 8 Seed the catalog (10 min)
Step 9 Smoke test (5 min)
Step 10 Record demo video (30 min)
For local development, production Kubernetes deployment, database rollback, and automated contract deployment, see the dedicated sections below.
This repo holds deployment tooling only; the application code lives in SynapsVault/backend, SynapsVault/frontend and SynapsVault/contracts.
| Path | Contents |
|---|---|
backend/Dockerfile, frontend/Dockerfile |
Image builds (the build context is a checkout of the app repo). Note: both images currently build with --legacy-peer-deps because of upstream peer dependency conflicts (x402-stellar vs @stellar/stellar-sdk) and a missing package-lock.json in SynapsVault/frontend; override with --build-arg NPM_FLAGS= once resolved upstream. |
k8s/ |
Kustomize manifests for production |
scripts/ |
Contract deploy/rollback, catalog seeding, smoke test, DB backup/restore/rollback |
monitoring/ |
Prometheus, Alertmanager and Grafana config |
.github/workflows/ |
CI, image build + K8s deploy, contract deploy, scheduled backups |
test/ |
Unit tests for the contract scripts (npm test) |
graph TD
Browser["Browser / Client"]
subgraph compose["docker-compose stack (this repo)"]
FE["frontend\n(Vite · :8080)"]
BE["backend\n(Node.js · :3000)"]
MIG["migrate\n(one-shot job)"]
PG["postgres\n(PostgreSQL 18 · :5432)"]
subgraph monitoring["monitoring profile"]
PGEXP["postgres-exporter"]
PROM["prometheus\n(:9090)"]
AM["alertmanager\n(:9093)"]
GF["grafana\n(:3001)"]
end
end
subgraph external["External services"]
SUPABASE["Supabase\n(storage)"]
STELLAR["Stellar / Soroban\n(testnet RPC)"]
OR["OpenRouter\n(LLM)"]
end
Browser -->|"HTTP :8080"| FE
Browser -->|"HTTP :3000"| BE
FE -->|"VITE_API_URL"| BE
BE -->|"DATABASE_URL"| PG
MIG -->|"DATABASE_URL (migrations)"| PG
BE -->|"SUPABASE_URL"| SUPABASE
BE -->|"SOROBAN_RPC_URL"| STELLAR
BE -->|"OPENROUTER_API_KEY"| OR
PGEXP -->|"scrape"| PG
PROM -->|"scrape"| BE
PROM -->|"scrape"| PGEXP
AM -->|"alerts"| PROM
GF -->|"datasource"| PROM
graph TD
Browser["Browser / Client"]
subgraph cluster["Kubernetes cluster (namespace: synapsvault)"]
ING["Ingress\n(nginx / cert-manager)"]
subgraph fe_svc["frontend"]
FE_DEP["Deployment\n(2 replicas)"]
end
subgraph be_svc["backend"]
BE_DEP["Deployment\n(2–5 replicas · HPA)"]
end
MIG_JOB["backend-migrate\n(Job — runs before rollout)"]
subgraph obs["observability"]
SM_BE["ServiceMonitor\n(backend)"]
SM_FE["ServiceMonitor\n(frontend)"]
PR["PrometheusRule\n(alert rules)"]
end
PDB["PodDisruptionBudget"]
CM["ConfigMap\n(non-secret env)"]
SEC["Secrets\n(DATABASE_URL, keys…)"]
end
subgraph external["External services"]
SUPABASE["Supabase\n(Postgres + storage)"]
STELLAR["Stellar / Soroban\n(mainnet RPC)"]
OR["OpenRouter\n(LLM)"]
PROM_OP["Prometheus Operator\n(cluster-level)"]
GHCR["GHCR\n(container images)"]
end
Browser -->|"HTTPS"| ING
ING -->|"/api"| BE_DEP
ING -->|"/"| FE_DEP
BE_DEP -->|"DATABASE_URL"| SUPABASE
BE_DEP -->|"SUPABASE_URL"| SUPABASE
BE_DEP -->|"SOROBAN_RPC_URL"| STELLAR
BE_DEP -->|"OPENROUTER_API_KEY"| OR
MIG_JOB -->|"DATABASE_URL"| SUPABASE
CM -->|"env injection"| BE_DEP
CM -->|"env injection"| FE_DEP
SEC -->|"env injection"| BE_DEP
SEC -->|"env injection"| MIG_JOB
PDB -->|"guards"| BE_DEP
SM_BE -->|"scraped by"| PROM_OP
SM_FE -->|"scraped by"| PROM_OP
PR -->|"loaded by"| PROM_OP
GHCR -->|"image pull"| FE_DEP
GHCR -->|"image pull"| BE_DEP
GHCR -->|"image pull"| MIG_JOB
Clone the application repos next to this one, then bring up the full stack from this repo's root (compose builds the images from the sibling checkouts):
git clone https://github.com/SynapsVault/backend ../backend
git clone https://github.com/SynapsVault/frontend ../frontend
cp .env.example .env
docker compose up --buildService URLs (ports are configurable in .env):
| Service | URL |
|---|---|
| Frontend | http://localhost:8080 |
| Backend | http://localhost:3000 |
| Postgres | localhost:5432 |
| Prometheus | http://localhost:9090 |
| Grafana | http://localhost:3001 |
Database migrations run automatically: the one-shot migrate service applies
them before backend starts. To re-run them manually:
docker compose run --rm migrateProduction deployments run on Kubernetes. Manifests live in k8s/
and the full runbook is in
docs/DEPLOY-KUBERNETES.md.
If a migration needs to be reverted, follow docs/DATABASE-ROLLBACK.md.
Contracts are deployed via
scripts/deploy-contracts.cjs
(npm run deploy:contracts) and the Deploy Contracts GitHub Actions
workflow (manual dispatch), which builds SynapsVault/contracts and deploys to
testnet or — after environment approval — mainnet. It needs the
DEPLOYER_SECRET_KEY secret (plus MAINNET_RPC_URL for mainnet). Roll back to
previous contract IDs with
scripts/rollback-contracts.cjs. See
docs/contract-deploy-guide.md.
The CI workflow runs on every pull request and push to main:
| Job | Checks |
|---|---|
| Lint GitHub workflows | actionlint (including shellcheck of run: blocks) |
| Lint shell scripts | shellcheck + bash -n on scripts/*.sh |
| Node scripts and tests | syntax check, npm test, CLI smoke tests |
| Validate Kubernetes manifests | kubectl kustomize + kubeconform -strict |
| Validate monitoring config | promtool, amtool, dashboard JSON |
| Validate docker-compose | docker compose config with .env.example |
| Build images | backend, backend-migrate and frontend images from the app repos |
Run the same checks locally before pushing:
npm ci && npm test
shellcheck scripts/*.sh
kubectl kustomize k8s/ > /dev/null
cp .env.example .env && docker compose config --quietInstall these before starting:
# Rust + wasm target
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
rustup target add wasm32-unknown-unknown
# Stellar CLI (successor to soroban-cli; scripts fall back to `soroban`)
# See https://developers.stellar.org/docs/tools/cli for install options
stellar --version
# Node.js 22.12+ (for scripts; required by @stellar/stellar-sdk 17)
nvm use # reads .nvmrc
node --version # should print v22.x.x or newer
npm ci
# Railway CLI (optional — can use dashboard instead)
npm install -g @railway/clicd deploy # this repository
node scripts/01-generate-keypairs.jsCopy all 5 keypairs into a password manager right now.
node scripts/02-fund-accounts.js \
G[DEPLOYER] G[BACKEND] G[PUBLISHER1] G[PUBLISHER2] G[BUYER1]Verify at: https://stellar.expert/explorer/testnet/account/G[DEPLOYER]
Every key is validated before Friendbot is contacted, an account that is
already funded counts as success, and the step exits non-zero if any account
could not be funded — so it can be chained with &&.
export DEPLOYER_SECRET=S[your_deployer_secret]
export BACKEND_PUBLIC=G[your_backend_public_key]
export CONTRACTS_DIR=../contracts # checkout of SynapsVault/contracts
bash scripts/03-deploy-contracts.sh # builds + deploys, ~20 min
source contract-ids.env
bash scripts/04-init-contracts.sh # calls init() on each contractOr do both steps in one go with the SDK-based deployer (no Stellar CLI needed; build the WASM first):
STELLAR_SECRET_KEY=S[your_deployer_secret] ADMIN_PUBLIC_KEY=G[your_backend_public_key] \
CONTRACTS_WASM_DIR=../contracts/target/wasm32-unknown-unknown/release \
npm run deploy:contracts -- --network testnetSave the contract IDs from contract-ids.env — you need them for the backend.
Follow the canonical guide: docs/railway-deploy.md
Before deploying, verify your .env has no unfilled placeholders:
node scripts/check-env.js --target railwayThe guide covers Supabase setup, Railway project creation, all required environment variables, migrations, verification, and troubleshooting.
In Railway shell:
pnpm drizzle-kit migrateYour frontend is already connected to GitHub. Just update the env vars:
VITE_API_URL = https://[your-railway-app].up.railway.app
Then trigger a redeploy in Vercel.
BACKEND_URL=https://[your-railway-app].up.railway.app \
PUBLISHER1_PUBLIC=G[PUBLISHER1] \
PUBLISHER2_PUBLIC=G[PUBLISHER2] \
node scripts/05-seed-catalog.jsThis idempotently publishes the 3 catalog resources (skipping any that already exist) and exits non-zero if validation or seeding fails.
BACKEND_URL=https://[your-railway-app].up.railway.app \
ADMIN_API_KEY=[your_admin_key] \
node scripts/06-smoke-test.jsAll 6 checks should pass. Fix any failures before submitting.
SCF requires a 3–5 minute demo video. Record:
- Show the live URL configured for your deployment
- Connect Freighter wallet
- Browse the catalog — show 3 real resources
- Click "Buy" on one resource — approve the USDC payment
- Show the content delivered
- Open Stellar Expert — show the contract interactions on-chain
- Show admin panel (
/admin/stats) — proves backend is live - Show GitHub repos — all 3, with recent commits and green CI
Upload to YouTube (unlisted) and paste the URL in the SCF submission form.
In docs/SCF-submission.md, replace these placeholders:
C[CONTRACT_ID]with your actual contract IDs fromcontract-ids.envhttps://synapsvault-backend.up.railway.appwith your actual Railway URL- the placeholder frontend URL with your actual deployed frontend URL
- Add the demo video URL in Section 12
Then submit the interest form at: https://communityfund.stellar.org/awards
See docs/MONITORING.md for the monitoring and alerting runbook — health checks, dashboards, alert thresholds, and on-call procedures.
See docs/ENVIRONMENT-VARIABLES.md for the full reference of backend, frontend, and contract environment variables.
See docs/BACKUP-RESTORE.md for the backup and restore runbook — database snapshots, contract state recovery, and disaster recovery procedures.
See docs/contract-deploy-guide.md for contract issues. See docs/railway-deploy.md for backend issues. See docs/DEPLOY-KUBERNETES.md for Kubernetes deployment issues. See docs/DATABASE-ROLLBACK.md for database rollback procedures.
For SCF questions: Stellar Dev Discord → #scf-general