Security: deep /api/health (DB+MinIO probes, 503 on failure) + /api/live; rate-limited public forms with honeypots; real client IP behind Traefik (--proxy-headers); TipTap URL sanitization on write (nh3); non-root containers + slim .dockerignore; prod MinIO console bound to 127.0.0.1; HSTS/nosniff on TLS routers; JWT 8h; no default credentials in code. Reliability: Alembic migrations (create_all baseline); graceful shutdown (exec uvicorn); hardened backups (checksums, sanity check, freshness gauge, optional rclone off-site) + restore.sh + hourly db-backup sidecar (ops profile); stack.sh ops tooling (deploy/logs/health/backup/restore/monitoring); .gitattributes LF for shell scripts. Orchestration: json-file log rotation, pinned images, per-service memory limits, edge/internal networks, Traefik ping/accesslog/metrics + flood limiter, conditional minio-setup, pgvector db image, one-shot db-init (umami + aramesh_chatbot databases). Chatbot: unified from site-chatbot branch (RAG + pgvector, SSE, ChatWidget). Monitoring: Prometheus/Alertmanager/Loki/Promtail/Grafana + cAdvisor, node/postgres/blackbox exporters, provisioned dashboards and 10 alert rules; alerts delivered to Bale (tapi.bale.ai — Telegram-compatible). Analytics: Umami on shared Postgres, build-time tracker injection, custom events (contact/newsletter/phone/chat/lang) via frontend/src/analytics.ts. Hygiene: CSV export for contact messages and subscribers; upload image optimization (Pillow); CI script + GitHub-compatible workflow; audit.sh; gen-env.sh for strong credentials.
112 lines
5.6 KiB
YAML
112 lines
5.6 KiB
YAML
# لایه تولید — TLS با Let's Encrypt و انتشار روی پورتهای استاندارد ۸۰/۴۳.
|
|
# پیشنیاز: متغیرهای DOMAIN و ACME_EMAIL در .env؛ بهعلاوه رکورد DNS دامنه به این سرور.
|
|
#
|
|
# bash scripts/stack.sh deploy-prod
|
|
# (معادل: docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build)
|
|
#
|
|
# همه درخواستهای HTTP به HTTPS هدایت میشوند و مسیریابیها فقط روی دامنه DOMAIN پاسخ میدهند.
|
|
# برای داشبوردها: analytics.DOMAIN (آنالیتیکس) و grafana.DOMAIN (مانیتورینگ، با فایل monitoring).
|
|
|
|
services:
|
|
traefik:
|
|
command: !override
|
|
- --providers.docker=true
|
|
- --providers.docker.exposedbydefault=false
|
|
- --providers.docker.constraints=Label(`com.docker.compose.project`, `aramesh-backend`)
|
|
- --entrypoints.web.address=:80
|
|
- --entrypoints.web.http.redirections.entrypoint.to=websecure
|
|
- --entrypoints.web.http.redirections.entrypoint.scheme=https
|
|
- --entrypoints.websecure.address=:443
|
|
- --certificatesresolvers.le.acme.email=${ACME_EMAIL:?ACME_EMAIL را در .env تنظیم کنید}
|
|
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
|
|
- --certificatesresolvers.le.acme.httpchallenge=true
|
|
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
|
|
- --ping=true
|
|
- --accesslog=true
|
|
- --metrics.prometheus=true
|
|
ports: !override
|
|
- "80:80"
|
|
- "443:443"
|
|
volumes: !override
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
- letsencrypt_data:/letsencrypt
|
|
|
|
# MinIO کنسول (رمز root) و API مستقیمش فقط از localhost سرور در دسترس باشند —
|
|
# از راه دور با تونل SSH: ssh -L 9001:127.0.0.1:9001 server
|
|
minio:
|
|
ports: !override
|
|
- "127.0.0.1:9001:9001"
|
|
- "127.0.0.1:19000:9000"
|
|
labels:
|
|
- traefik.enable=true
|
|
- traefik.http.routers.media.rule=Host(`${DOMAIN}`) && PathPrefix(`/media`)
|
|
- traefik.http.routers.media.entrypoints=websecure
|
|
- traefik.http.routers.media.tls.certresolver=le
|
|
- traefik.http.routers.media.priority=100
|
|
- traefik.http.routers.media.middlewares=media-rewrite,sec-headers
|
|
- traefik.http.middlewares.media-rewrite.replacepathregex.regex=^/media/(.*)
|
|
- traefik.http.middlewares.media-rewrite.replacepathregex.replacement=/aramesh-media/$$1
|
|
- traefik.http.services.media.loadbalancer.server.port=9000
|
|
|
|
backend:
|
|
environment:
|
|
# سایت هممبدأ است — CORS لازم نیست
|
|
CORS_ORIGINS: '[]'
|
|
SITE_URL: https://${DOMAIN:?DOMAIN را در .env تنظیم کنید}
|
|
labels:
|
|
- traefik.enable=true
|
|
- traefik.http.routers.api.rule=Host(`${DOMAIN}`) && PathPrefix(`/api`)
|
|
- traefik.http.routers.api.entrypoints=websecure
|
|
- traefik.http.routers.api.tls.certresolver=le
|
|
- traefik.http.routers.api.middlewares=api-flood,sec-headers
|
|
- traefik.http.routers.seo.rule=Host(`${DOMAIN}`) && (PathPrefix(`/blog/`) || Path(`/blog`) || PathPrefix(`/projects/`) || Path(`/projects`) || Path(`/en`) || PathPrefix(`/en/`) || PathPrefix(`/robots.txt`) || PathPrefix(`/sitemap.xml`))
|
|
- traefik.http.routers.seo.entrypoints=websecure
|
|
- traefik.http.routers.seo.tls.certresolver=le
|
|
- traefik.http.routers.seo.middlewares=sec-headers
|
|
|
|
frontend:
|
|
labels:
|
|
- traefik.enable=true
|
|
- traefik.http.routers.web.rule=Host(`${DOMAIN}`) && PathPrefix(`/`)
|
|
- traefik.http.routers.web.entrypoints=websecure
|
|
- traefik.http.routers.web.tls.certresolver=le
|
|
- traefik.http.routers.web.middlewares=sec-headers
|
|
# تعریف یکبارهٔ میانافزار هدرهای امنیتی — روی همهٔ روترهای websecure استفاده میشود
|
|
- traefik.http.middlewares.sec-headers.headers.stsSeconds=31536000
|
|
- traefik.http.middlewares.sec-headers.headers.stsIncludeSubdomains=true
|
|
- traefik.http.middlewares.sec-headers.headers.stsPreload=true
|
|
- traefik.http.middlewares.sec-headers.headers.contentTypeNosniff=true
|
|
|
|
chatbot:
|
|
labels:
|
|
- traefik.enable=true
|
|
- traefik.http.routers.chat.rule=Host(`${DOMAIN}`) && PathPrefix(`/chat`)
|
|
- traefik.http.routers.chat.entrypoints=websecure
|
|
- traefik.http.routers.chat.tls.certresolver=le
|
|
- traefik.http.routers.chat.priority=100
|
|
- traefik.http.routers.chat.middlewares=api-flood,sec-headers
|
|
|
|
umami:
|
|
labels:
|
|
- traefik.enable=true
|
|
- traefik.http.routers.analytics.rule=Host(`analytics.${DOMAIN}`)
|
|
- traefik.http.routers.analytics.entrypoints=websecure
|
|
- traefik.http.routers.analytics.tls.certresolver=le
|
|
- traefik.http.routers.analytics.middlewares=sec-headers
|
|
- traefik.http.services.analytics.loadbalancer.server.port=3000
|
|
|
|
# داشبورد Grafana (docker-compose.monitoring.yml) در تولید با TLS نیاز به روتر
|
|
# Host(`grafana.DOMAIN`) دارد — چون آن سرویس فقط با فایل monitoring بالا میآید،
|
|
# برچسبهایش را در override سرور بگذارید (README، بخش مانیتورینگ):
|
|
# docker-compose.override.yml روی سرور:
|
|
# services:
|
|
# grafana:
|
|
# labels:
|
|
# - traefik.http.routers.grafana.rule=Host(`grafana.YOURDOMAIN`)
|
|
# - traefik.http.routers.grafana.entrypoints=websecure
|
|
# - traefik.http.routers.grafana.tls.certresolver=le
|
|
# - traefik.http.services.grafana.loadbalancer.server.port=3000
|
|
|
|
volumes:
|
|
letsencrypt_data:
|