Files
meezi/DEPLOY.md
T
soroush.asadi 289c808257 Rename public discovery app from "finder" to "koja"
Rebrand the public café-discovery app: directories web/finder→web/koja and
docker/finder→docker/koja, plus all service wiring (docker-compose, Caddy
subdomain koja.meezi.ir, env vars KOJA_PORT / NEXT_PUBLIC_KOJA_URL, CI
workflows) and the app's display name (Koja / کجا).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 17:02:22 +03:30

8.8 KiB
Raw Blame History

Meezi — Deployment Guide

Architecture overview

Server: 171.22.25.73
│
├── Gitea          :3000   ← source control + CI runner
├── Nexus          :8081   ← package mirror (NuGet, npm, Docker)
│
├── meezi-api      :5080   ← .NET main API
├── meezi-admin-api:5081   ← .NET admin API
├── meezi-web      :3101   ← Next.js cafe owner dashboard
├── meezi-admin-web:3102   ← Next.js super-admin panel
├── meezi-website  :3010   ← Next.js marketing website
├── meezi-koja     :3103   ← Next.js public discovery (Koja)
├── meezi-db       :5434   ← PostgreSQL (not internet-facing)
└── meezi-redis    :6381   ← Redis (not internet-facing)

Docker Compose files:

docker-compose.yml          main services (postgres, redis, api, web, website, koja)
docker-compose.admin.yml    admin overlay (+admin-api, +admin-web)
docker-compose.mirror.yml   Nexus mirror — run once separately, stays running
docker-compose.caddy.yml    Caddy HTTPS proxy — add when domain is ready

First-time setup

Step 1 — Set the ENV_FILE secret in Gitea

Open in browser:

http://171.22.25.73:3000/soroushdes/meezi/settings/secrets

Click Add Secret, name it exactly ENV_FILE, paste the block below, click Save.

ASPNETCORE_ENVIRONMENT=Production

# ── Database ──────────────────────────────────────────────────────────────────
DB_PASSWORD=YOUR_STRONG_PASSWORD
DB_CONNECTION_STRING=Host=postgres;Port=5432;Database=meezi;Username=meezi;Password=YOUR_STRONG_PASSWORD

# ── JWT ───────────────────────────────────────────────────────────────────────
# Generate: openssl rand -hex 32
JWT_KEY=YOUR_64_CHAR_HEX

# ── Migrations ────────────────────────────────────────────────────────────────
RUN_MIGRATIONS=true

# ── Public URLs ───────────────────────────────────────────────────────────────
# ⚠️  NEXT_PUBLIC_* are baked into Next.js images at build time.
#     Changing them requires a CI re-run to rebuild images.
NEXT_PUBLIC_API_URL=http://171.22.25.73:5080
NEXT_PUBLIC_ADMIN_API_URL=http://171.22.25.73:5081
NEXT_PUBLIC_SITE_URL=http://171.22.25.73:3010
NEXT_PUBLIC_KOJA_URL=http://171.22.25.73:3103

APP_QR_BASE_URL=http://171.22.25.73:3101
BILLING_DASHBOARD_URL=http://171.22.25.73:3101

# ── CORS ──────────────────────────────────────────────────────────────────────
CORS_ORIGIN_0=http://171.22.25.73:3101
CORS_ORIGIN_1=http://171.22.25.73:3010
CORS_ORIGIN_2=http://171.22.25.73:3103
CORS_ADMIN_ORIGIN_0=http://171.22.25.73:3102

# ── Host ports ────────────────────────────────────────────────────────────────
API_PORT=5080
ADMIN_API_PORT=5081
WEB_PORT=3101
ADMIN_WEB_PORT=3102
WEBSITE_PORT=3010
KOJA_PORT=3103
POSTGRES_PORT=5434
REDIS_PORT=6381

# ── Payment: ZarinPal ─────────────────────────────────────────────────────────
ZARINPAL_MERCHANT_ID=
ZARINPAL_SANDBOX=true

# ── SMS: Kavenegar ────────────────────────────────────────────────────────────
# Empty = OTP printed to API container logs (ok for testing)
KAVENEGAR_API_KEY=

# ── Snappfood webhook ─────────────────────────────────────────────────────────
SNAPPFOOD_WEBHOOK_SECRET=YOUR_RANDOM_SECRET

The actual generated values were set directly in Gitea during initial setup. To view or rotate them: Gitea → Settings → Secrets → ENV_FILE → Edit.


Step 2 — Trigger the first deployment

On the server:

cd ~/meezi
git pull origin main
git push gitea main

Watch the pipeline:

http://171.22.25.73:3000/soroushdes/meezi/actions

CI takes ~510 minutes: builds 6 Docker images, runs all checks, then deploys.


Service URLs (no domain, IP-based)

Service URL
Marketing website http://171.22.25.73:3010/fa
Cafe owner dashboard http://171.22.25.73:3101/fa/login
Public Koja http://171.22.25.73:3103/fa
Super-admin panel http://171.22.25.73:3102/fa/admin/login
Main API (Swagger) http://171.22.25.73:5080/swagger
Admin API (Swagger) http://171.22.25.73:5081/swagger
Gitea http://171.22.25.73:3000
Nexus http://171.22.25.73:8081

Day-to-day: pushing code

git add .
git commit -m "your message"
git push origin main     # GitHub backup
git push gitea main      # ← triggers CI + auto-deploy

Every push to main on Gitea runs all CI jobs. If all pass, deploy runs automatically.


Checking containers on the server

# Status of all app containers
docker compose -f docker-compose.yml -f docker-compose.admin.yml ps

# Live logs for a service
docker compose logs -f api
docker compose logs -f web
docker compose logs -f admin-api

# All containers on the machine
docker ps

When domain is ready (tomorrow)

1. Point DNS at the server

Create these A records — all pointing to 171.22.25.73:

Hostname Service
meezi.ir Marketing website
app.meezi.ir Cafe dashboard
api.meezi.ir Main API
koja.meezi.ir Koja
admin.meezi.ir Admin panel
admin-api.meezi.ir Admin API

2. Update ENV_FILE secret in Gitea

Remove the IP-based section and replace with:

# ── Domain ────────────────────────────────────────────────────────────────────
DOMAIN=meezi.ir
ACME_EMAIL=you@example.com

NEXT_PUBLIC_API_URL=https://api.meezi.ir
NEXT_PUBLIC_ADMIN_API_URL=https://admin-api.meezi.ir
NEXT_PUBLIC_SITE_URL=https://meezi.ir
NEXT_PUBLIC_KOJA_URL=https://koja.meezi.ir

APP_QR_BASE_URL=https://app.meezi.ir
BILLING_DASHBOARD_URL=https://app.meezi.ir

CORS_ORIGIN_0=https://app.meezi.ir
CORS_ORIGIN_1=https://meezi.ir
CORS_ORIGIN_2=https://koja.meezi.ir
CORS_ADMIN_ORIGIN_0=https://admin.meezi.ir

# Remove all PORT= lines — Caddy is the only public endpoint

3. Update CI deploy steps

In .gitea/workflows/ci-cd.yml find the deploy job and add docker-compose.caddy.yml:

- name: Start all services
  run: |
    docker compose \
      -f docker-compose.yml \
      -f docker-compose.admin.yml \
      -f docker-compose.caddy.yml \
      up -d --remove-orphans

Open port 80 and 443 in the server firewall:

ufw allow 80
ufw allow 443

4. Push to Gitea

git push gitea main

CI rebuilds all images with the new domain URLs baked in. Caddy starts and gets Let's Encrypt certificates automatically — no certbot or manual renewal needed.


Secrets to rotate before going live

Secret How to generate
JWT_KEY openssl rand -hex 32
DB_PASSWORD Strong random password — update both DB_PASSWORD= and inside DB_CONNECTION_STRING=
ZARINPAL_MERCHANT_ID panel.zarinpal.com → API → MerchantID
ZARINPAL_SANDBOX Change to false for live payments
KAVENEGAR_API_KEY kavenegar.com dashboard

After updating any secret in Gitea: push a commit to trigger a redeploy.


Mirror server (Nexus)

Nexus runs separately and should always be running:

# Start (first time or after server reboot)
docker compose -f docker-compose.mirror.yml up -d

# Health check
curl -s http://localhost:8081/service/rest/v1/status

Provisioned repos:

Repo Type Upstream
nuget-group NuGet group Liara → Runflare fallback
npm-group npm group Liara → Runflare fallback
docker-hub-proxy Docker proxy :5000 Docker Hub
mcr-proxy Docker proxy :5002 mcr.microsoft.com
pypi-proxy PyPI proxy Liara
ubuntu-proxy APT proxy Liara (jammy)
ubuntu-security-proxy APT proxy Liara (jammy-security)

To add new mirrors or switch upstreams:

./mirrors/nexus/add-liara-mirrors.sh
./mirrors/nexus/update-docker-upstream.sh