8.7 KiB
Deployment Guide
This document covers production deployment for the omo-bot Coolify project with two separate resources:
omo-bot-backend(Node.js bot + Admin API)omo-bot-dashboard(React/Vite static dashboard fromadmin-dashboard/)
Deployment Topology
Recommended domains:
api.openmicodyssey.com->omo-bot-backendadmin.openmicodyssey.com->omo-bot-dashboard
Recommended edge topology:
- Internet -> Nginx reverse proxy -> Coolify resources
- Nginx routes by hostname to backend/dashboard resource domains or internal ports
Optional same-origin pattern:
admin.openmicodyssey.comserves dashboard and reverse-proxies/admin/*+/healthto backend.
1. Create Coolify Project and Resources
- In Coolify, create (or open) project
omo-bot. - Add resource
omo-bot-backendfrom this repository:- Build context: repository root
- Start command:
npm start - Internal port:
8787(Admin API)
- Add resource
omo-bot-dashboardfrom this repository:- Base directory:
admin-dashboard - Build command:
npm run build - Publish directory:
/ - Internal port:
80(typical static web port behind Coolify)
- Base directory:
- Configure domains for each resource and enable TLS certificates.
1.1 Nginx Reverse Proxy in Front of Coolify
Use this pattern when you want one public edge server in front of Coolify-managed services.
Recommended flow:
- Public DNS for
api.openmicodyssey.comandadmin.openmicodyssey.compoints to Nginx host. - Nginx terminates TLS and forwards traffic to Coolify resource domains or private IP:port targets.
- Coolify resources stay private or restricted to internal network where possible.
Example host-based routing:
server {
listen 80;
server_name api.openmicodyssey.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name api.openmicodyssey.com;
ssl_certificate /etc/letsencrypt/live/api.openmicodyssey.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.openmicodyssey.com/privkey.pem;
location / {
proxy_pass http://coolify-backend.internal:8787;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
server {
listen 80;
server_name admin.openmicodyssey.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name admin.openmicodyssey.com;
ssl_certificate /etc/letsencrypt/live/admin.openmicodyssey.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/admin.openmicodyssey.com/privkey.pem;
location / {
proxy_pass http://coolify-dashboard.internal:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
If you prefer same-origin routing for dashboard + API through one public domain, proxy location /admin/ and location /health to backend while keeping / on dashboard.
Recommended OAuth callback route:
https://admin.openmicodyssey.com/oauth/discord/callback
If you use this callback path, make sure dashboard hosting serves the SPA entrypoint for that route instead of returning 404.
2. Configure Runtime Variables
Backend (omo-bot-backend) minimum:
DATABASE_URLCONFIG_DB_ENABLED=true- Any temporary legacy vars needed for first-time
db:seed
Dashboard (omo-bot-dashboard) minimum:
VITE_ADMIN_API_BASE_URL(for split-domain deployment, set tohttps://api.openmicodyssey.com)VITE_DISCORD_CLIENT_IDVITE_DISCORD_REDIRECT_URI(recommended:https://admin.openmicodyssey.com/oauth/discord/callback)
OAuth alignment:
- Discord Developer Portal redirect URI must exactly match the dashboard callback URL.
- Config DB key
OAUTH_BRIDGE_REDIRECT_URImust match the same callback URL. - Dashboard env
VITE_DISCORD_REDIRECT_URImust match the same callback URL.
Discord OAuth authorize URL shape:
https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=https%3A%2F%2Fadmin.openmicodyssey.com%2Foauth%2Fdiscord%2Fcallback&scope=identify%20guilds&prompt=consent
3. Get Coolify API Token and Resource UUIDs
- In Coolify, open
Keys & Tokens->API Tokensand create a token. - Copy your Coolify base URL (example:
https://coolify.yourdomain.com). - Find resource UUID for:
omo-bot-backendomo-bot-dashboard
Deploy API endpoint used by CI:
GET /api/v1/deploy?uuid=<resource_uuid>- Authorization:
Bearer <COOLIFY_API_TOKEN>
3.1 Token Requirements (Important)
If CI fails with Coolify API token rejected during preflight (HTTP 403), token exists but lacks access to API target.
Use this checklist:
- Create token in same Coolify instance that hosts
omo-bot-backendandomo-bot-dashboardresources. - Ensure token owner has access to project/team containing those resources.
- Ensure token is API token (not UI session token/cookie).
- Set
COOLIFY_BASE_URLto Coolify root URL only (example:https://coolify.yourdomain.com), no extra path like/project/.... - Rotate token if unsure, then update Gitea secret
COOLIFY_API_TOKEN.
Required permissions to grant (minimum):
- Project/resource read access for both deploy targets (
omo-bot-backend,omo-bot-dashboard) - Deploy/trigger permission on both resources
- Team/project membership that includes those resources (not observer/read-only)
If your Coolify token form shows these permissions:
- root
- write
- deploy
- read
- read:sensitive
Use this selection for CI deploy token:
- Required: read + deploy
- Optional: write (only if your instance still blocks deploy with read+deploy)
- Avoid for CI: root
- Optional and sensitive: read:sensitive (only needed when debugging APIs that must return secrets/log internals)
Tip: Start with read + deploy. If preflight still returns 403, temporarily add write to verify scope mismatch, then reduce to least-privilege that still works.
Preflight check from local shell (should NOT return 401 or 403):
curl -i \
-H "Authorization: Bearer $COOLIFY_API_TOKEN" \
"$COOLIFY_BASE_URL/api/v1/deploy"
Resource trigger checks (match CI fallback logic):
# Variant 1: GET + Bearer
curl -i -G \
-H "Authorization: Bearer $COOLIFY_API_TOKEN" \
--data-urlencode "uuid=$COOLIFY_RESOURCE_UUID" \
"$COOLIFY_BASE_URL/api/v1/deploy"
# Variant 2: POST JSON + Bearer
curl -i -X POST \
-H "Authorization: Bearer $COOLIFY_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"uuid\":\"$COOLIFY_RESOURCE_UUID\"}" \
"$COOLIFY_BASE_URL/api/v1/deploy"
# Variant 3: GET + raw Authorization token
curl -i -G \
-H "Authorization: $COOLIFY_API_TOKEN" \
--data-urlencode "uuid=$COOLIFY_RESOURCE_UUID" \
"$COOLIFY_BASE_URL/api/v1/deploy"
If all variants return 403, issue is permissions/scope in Coolify, not Gitea secret injection.
4. Configure Gitea Action Secrets/Variables
In Gitea repository settings, add Actions secrets:
COOLIFY_BASE_URLCOOLIFY_API_TOKENCOOLIFY_RESOURCE_UUID_BOTCOOLIFY_RESOURCE_UUID_DASHBOARD
Current workflow in .gitea/workflows/ci-cd.yml triggers backend and dashboard deploys separately via the Coolify Deploy API.
5. Domain and DNS Checklist
- Create DNS records to Nginx edge host (or directly to Coolify if no edge proxy):
Arecord for root/subdomain targetsCNAMEwhere appropriate
- Verify certificates issued on Nginx or Coolify, depending on TLS termination point.
- Verify Nginx upstreams match deployed ports:
- backend ->
8787 - dashboard ->
80
- Verify OAuth callback route resolves through dashboard hosting:
https://admin.openmicodyssey.com/oauth/discord/callback
- Verify dashboard can call API at configured
VITE_ADMIN_API_BASE_URL. - Verify API health endpoint over TLS:
GET https://api.openmicodyssey.com/health
6. Post-Deploy Verification
npm run register:commands(if bot app/guild IDs changed).- Confirm bot online in Discord and slash commands visible.
- Open dashboard URL and run OAuth login flow.
- Confirm Discord redirects back to
https://admin.openmicodyssey.com/oauth/discord/callbackwithout404. - Validate Admin API auth behavior with and without bearer token.
7. Coolify 403 Troubleshooting
When CI fails in Validate Coolify API access with HTTP 403:
- Confirm token belongs to correct Coolify instance and team/project.
- Confirm resource UUIDs are from same instance and still valid.
- Re-run local preflight curl from section 3.1.
- Recreate token and update Gitea secret.
- Re-run pipeline.
Current CI workflow already prints sanitized response snippets for deploy failures and tries auth/method fallback variants in .gitea/workflows/ci-cd.yml.