docs: record production deployment and maintenance

This commit is contained in:
Jesse_Chen
2026-07-16 10:13:04 +08:00
parent 66bc420edb
commit 6f138ebd45
4 changed files with 178 additions and 48 deletions
+130 -36
View File
@@ -1,62 +1,156 @@
# Railway deployment
# Production deployment and maintenance
Create services named `web` and `api` from this repository. Leave both **Root Directory** and **Start Command** empty; the Dockerfiles bind `0.0.0.0` and read `$PORT`.
This file is the operational source of truth for the current Jyotisha demo deployment.
| Service | Required variable | Healthcheck Path |
| --- | --- | --- |
| `web` | `RAILWAY_DOCKERFILE_PATH=/deploy/railway-web.Dockerfile` | `/login` |
| `api` | `RAILWAY_DOCKERFILE_PATH=/deploy/railway-api.Dockerfile` | `/api/health` |
## Current production
## `api` variables
| Item | Value |
| --- | --- |
| Public domain | `https://jyotisha.chat` |
| DNS | Spaceship nameservers (`launch1.spaceship.net`, `launch2.spaceship.net`) |
| Server | Hong Kong VPS, Ubuntu 22.04 x86_64 |
| Public host | `103.117.123.53` |
| SSH | port `22000`, public-key authentication only |
| Capacity | 1 vCPU / 2 GB RAM / 40 GB disk / 5 Mbps |
| App directory | `/opt/jyotisha-app` |
| Environment file | `/opt/jyotisha-app/.env.production` (`0600`) |
| Source repository | `https://github.com/jesse-ux/Jyotisha.git` |
| Supabase project | `vtvnfqmonbfuxmqkqdlc` |
```dotenv
PORT=5200
This machine is suitable for a client demo and low concurrency. Supabase and the model provider stay managed externally; do not self-host them on this VPS.
## Architecture
```text
Spaceship DNS
-> Caddy :80/:443
-> web:3000 (Next.js + Mastra, Docker-private)
-> api:5200 (Python Jyotish API, Docker-private)
-> Swiss Ephemeris / local engine
-> VedAstro gateway with local fallback
-> Supabase Cloud
-> external OpenAI-compatible model API
```
`PORT` is set explicitly so the web service can reference the private API port. Railway healthchecks and the container command use the same value. No public API domain or legacy `jyotish-app/` service is needed.
Only Caddy publishes host ports. Ports `3000` and `5200` must remain private.
## `web` variables
## DNS and Supabase Auth
Required:
Spaceship resource records:
```text
A @ 103.117.123.53
CNAME www jyotisha.chat
```
Supabase Authentication URL Configuration:
```text
Site URL: https://jyotisha.chat
Redirect URLs: https://jyotisha.chat/**
https://www.jyotisha.chat/**
```
Before changing Caddy to the domain, verify the authoritative DNS result:
```bash
dig +short @launch1.spaceship.net A jyotisha.chat
```
It must return `103.117.123.53`. Caddy provisions and renews HTTPS automatically after DNS resolves.
## Production environment
`.env.production` combines the backend and frontend server variables. Required groups:
```dotenv
JYOTISH_API_BASE=http://${{api.RAILWAY_PRIVATE_DOMAIN}}:${{api.PORT}}
SITE_ADDRESS=https://jyotisha.chat
JYOTISH_API_BASE=http://api:5200
NEXT_PUBLIC_SUPABASE_URL=...
NEXT_PUBLIC_SUPABASE_ANON_KEY=...
SUPABASE_SERVICE_ROLE_KEY=...
```
ADMIN_EMAILS=...
For AI responses, configure either:
```dotenv
# Either OpenAI:
OPENAI_API_KEY=...
MASTRA_MODEL=openai/gpt-5-mini
```
MASTRA_MODEL=...
or an OpenAI-compatible provider:
```dotenv
# Or an OpenAI-compatible provider:
LLM_BASE_URL=...
LLM_API_KEY=...
LLM_MODEL=...
# LLM_PROVIDER_ID=third-party
# Optional VedAstro official upstream; local fallback remains available:
VEDASTRO_API_ENDPOINT=...
VEDASTRO_API_KEY=...
```
`ADMIN_EMAILS=admin@example.com,ops@example.com` is required to use `/admin/codes`. Generate a public domain only for `web`.
Never commit `.env.production`, `SUPABASE_SERVICE_ROLE_KEY`, model keys, user JWTs, SSH private keys or passwords. `NEXT_PUBLIC_SUPABASE_ANON_KEY` is intentionally public; authorization is enforced by Supabase RLS and server-side checks.
## After the first deploy
1. Add the generated `web` domain to Supabase Auth **Site URL** and **Redirect URLs**.
2. Open `/login`, sign in with an address listed in `ADMIN_EMAILS`, then verify `/admin/codes`.
3. Keep `api` private; verify its `/api/health` from Railway logs or the `web` service.
## Small VPS deployment
For a single low-traffic server, keep Supabase managed and run only the Web and API services:
## Connect and inspect
```bash
docker compose --env-file .env.production -f deploy/docker-compose.server.yml up -d --build
ssh -p 22000 root@103.117.123.53
cd /opt/jyotisha-app
COMPOSE='docker compose --env-file .env.production -f deploy/docker-compose.server.yml'
$COMPOSE ps
$COMPOSE logs --tail=100 api web caddy
free -h
docker stats --no-stream
```
Set `SITE_ADDRESS=http://SERVER_IP` for initial HTTP testing. Replace it with the production domain after DNS resolves; Caddy will then provision HTTPS automatically.
The server has a persistent 2 GB `/swapfile`. UFW permits only SSH `22000/tcp`, HTTP `80/tcp`, HTTPS `443/tcp`, and the pre-existing WireGuard `51820/udp` rule.
## Deploy an update from the maintainer Mac
The GitHub repository is private and the VPS does not currently have a GitHub deploy key. Deploy the tracked tree without copying local secrets:
```bash
cd /Users/jesse/Downloads/Copse/astrology/yinduzhanxing
git status --short --branch
git archive --format=tar.gz --output=/tmp/jyotisha.tar.gz HEAD
scp -P 22000 /tmp/jyotisha.tar.gz root@103.117.123.53:/tmp/jyotisha.tar.gz
ssh -p 22000 root@103.117.123.53 \
'cd /opt/jyotisha-app && tar -xzf /tmp/jyotisha.tar.gz && rm -f /tmp/jyotisha.tar.gz && docker compose --env-file .env.production -f deploy/docker-compose.server.yml up -d --build'
```
`git archive` does not include ignored `.env` files. Extraction preserves `/opt/jyotisha-app/.env.production`.
## Verification
```bash
curl -fsS https://jyotisha.chat/login >/dev/null
curl -fsS -o /dev/null -w '%{http_code}\n' https://jyotisha.chat/api/account
```
The second command should return `401` while logged out. Verify the private Python API from inside the web container:
```bash
ssh -p 22000 root@103.117.123.53 \
'cd /opt/jyotisha-app && docker compose --env-file .env.production -f deploy/docker-compose.server.yml exec -T web node -e "fetch(\"http://api:5200/api/health\").then(async r=>{console.log(r.status); console.log(await r.text())})"'
```
Expected: HTTP `200`, `"status": "ok"`, and `"swisseph_available": true`. Public access to `103.117.123.53:5200` must fail.
Then manually verify: OTP login, onboarding/profile persistence, chat-session persistence, code redemption, admin code generation, streaming response, one-credit charge, and refund on failure before the first output chunk.
## Common operations
```bash
# Restart without rebuilding
docker compose --env-file .env.production -f deploy/docker-compose.server.yml up -d
# Rebuild only the web container
docker compose --env-file .env.production -f deploy/docker-compose.server.yml up -d --build web caddy
# Rebuild only the Python API
docker compose --env-file .env.production -f deploy/docker-compose.server.yml up -d --build api
# Follow logs
docker compose --env-file .env.production -f deploy/docker-compose.server.yml logs -f --tail=100 api web caddy
```
## Optional Railway deployment
Railway is not the current production target. If needed, create `web` and `api` services from the same repository using `deploy/railway-web.Dockerfile` and `deploy/railway-api.Dockerfile`; keep `api` private and set the web service's `JYOTISH_API_BASE` to Railway's private API hostname.