spomeni/docs/coolify.md
2026-08-03 19:34:36 +02:00

376 lines
13 KiB
Markdown

# Deploying SpomeniQR on Coolify
This guide covers deploying SpomeniQR to a VPS using [Coolify](https://coolify.io/) — a self-hosting platform similar to Heroku/Vercel that manages Docker deployments, SSL, and databases.
## Prerequisites
- A VPS with Docker installed (Coolify handles this)
- Coolify installed on your VPS (see [coolify.io/docs](https://coolify.io/docs/installation))
- Domain `testbed.mk` with DNS configured:
- **A record** `@` → your VPS IP
- **A record** `*` → your VPS IP (wildcard for subdomains)
- A Contabo S3 bucket set up with public read policy
## Architecture Overview
Coolify will run two containers:
1. **app** — Next.js standalone build (Dockerfile)
2. **db** — PostgreSQL 16 (Coolify managed database)
We do **not** deploy Nginx via Docker — Coolify has its own reverse proxy (Traefik/Caddy) that handles SSL, subdomain routing, and the `X-Subdomain` header.
```
Internet
├── *.testbed.mk ──► Coolify Proxy (:80/:443)
│ ├── Extracts subdomain → sets X-Subdomain header
│ └── Proxies to app:3000
└── testbed.mk ──► Coolify Proxy ──► app:3000
```
## Step 1: Add a New Project in Coolify
1. Open your Coolify dashboard
2. Click **+ Add New Project**
3. Name it `SpomeniQR`
## Step 2: Create the Database
1. Inside the project, click **+ Add New Resource** → **Database**
2. Select **PostgreSQL**
3. Configure:
- **Name:** `spomeniqr-db`
- **PostgreSQL Version:** 16
- **Database Name:** `monuments`
- **Username:** `postgres`
- **Password:** generate a strong password or set your own
4. Click **Deploy**
5. After deployment, note the **Internal Connection String** — it looks like:
```
postgresql://postgres:YOUR_PASSWORD@spomeniqr-db:5432/monuments
```
You'll need this for `DATABASE_URL`.
## Step 3: Add the Application
1. Inside the project, click **+ Add New Resource** → **Application**
2. Select **Public Repository** (or **Private** if your repo is private)
3. Configure:
- **Name:** `spomeniqr`
- **Repository URL:** your Git repo URL
- **Branch:** `main`
- **Build Pack:** **Docker** (recommended) or **Nixpacks**
### Option A: Docker (Recommended — uses the project's Dockerfile)
Set **Build Pack** to **Docker**. Coolify will use the `Dockerfile` in the repo root.
No additional configuration needed — it already includes Prisma generate,
`next build`, migrations (`prisma migrate deploy`), and automatic super-admin
provisioning on startup. This is the verified path (tested end-to-end locally).
**Note:** If using the Docker build pack, the `DATABASE_URL` must use
`spomeniqr-db` as the host (Coolify internal network), not `localhost`.
**Build-time variables:** the `NEXT_PUBLIC_*` env vars are inlined into the
browser bundle during `next build`. The Dockerfile declares matching `ARG`s, so
mark every `NEXT_PUBLIC_*` variable as a **build variable** in Coolify (the
checkbox on each env var). Without this, client-side Clerk auth (sign-in /
sign-up) will have no publishable key. Server-side middleware also reads them at
runtime, so keep them set as regular runtime vars too.
### Option B: Nixpacks (auto-detected)
Leave the build pack as **Nixpacks**. Coolify will auto-detect Next.js and build it.
Add these **Build Commands:**
```
npx prisma generate && npm run build
```
Add this **Start Command:**
```
npx prisma migrate deploy && node .next/standalone/server.js
```
**Note:** With a custom start command the auto-seed does not run; provision the
super-admin manually after the first deploy with `npx prisma db seed` (or see the
seed note in Step 4).
## Step 4: Configure Environment Variables
In the application settings, go to **Environment Variables** and add:
```env
# Database — use the Coolify internal connection string
DATABASE_URL=postgresql://postgres:YOUR_PASSWORD@spomeniqr-db:5432/monuments
# Clerk
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_...
CLERK_SECRET_KEY=sk_live_...
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/
# Contabo S3
S3_ENDPOINT=https://eu2.contabostorage.com
S3_REGION=eu-2
S3_ACCESS_KEY_ID=your-access-key
S3_SECRET_ACCESS_KEY=your-secret-key
S3_BUCKET_NAME=monuments-images
# App
NEXT_PUBLIC_APP_URL=https://testbed.mk
NEXT_PUBLIC_APP_DOMAIN=testbed.mk
# Admin session signing secret (64+ random hex chars; openssl rand -hex 32)
ADMIN_SESSION_SECRET=your-random-64-char-secret
# Super-admin — username + BCRYPT HASH (not plaintext). Generate with:
# node -e "import('bcryptjs').then(b => b.default.hash('YOUR_PASSWORD', 12).then(console.log))"
# Coolify UI env vars are passed to the container literally — no `$` escaping needed.
SUPER_ADMIN_USERNAME=super
SUPER_ADMIN_PASSWORD_HASH=$2b$12$REPLACE_WITH_BCRYPT_HASH
# Node
NODE_ENV=production
```
**Important:**
- `DATABASE_URL` must point to the Coolify **internal** hostname (`spomeniqr-db`), not `localhost`.
- Use your **production** Clerk keys (`pk_live_` / `sk_live_`), not the test ones.
- Use a **different, strong** super-admin password than your local development one.
- Generate the hash **on your dev machine** (in the project, so it uses the
project's `bcryptjs`) and paste it **verbatim** — Coolify passes values
literally, no `$$` escaping:
```
node -e "import('bcryptjs').then(b => b.default.hash('YOUR_PASSWORD', 12).then(console.log))"
```
- Mark **every `NEXT_PUBLIC_*` variable as a build variable** (checkbox) so it is
inlined into the client bundle at `next build`. Server-only vars
(`CLERK_SECRET_KEY`, `DATABASE_URL`, `ADMIN_SESSION_SECRET`,
`SUPER_ADMIN_PASSWORD_HASH`, `S3_*`) stay as normal runtime variables.
- The `super` admin row is provisioned automatically on container start by
`scripts/start.sh` (`node prisma/seed.cjs` after migrations). If you instead
use a custom Nixpacks start command (Option A below), run the seed manually
after the first deploy: `npx prisma db seed`.
## Step 5: Configure Domain & Subdomain Routing
### Set the Main Domain
1. In the application settings, go to **Configuration****Domains**
2. Add: `testbed.mk`
3. Enable **HTTPS** — Coolify will auto-provision a Let's Encrypt certificate
### Enable Wildcard Subdomain Routing
This is the critical part. Coolify's proxy needs to:
1. Accept requests for `*.testbed.mk`
2. Extract the subdomain and pass it as the `X-Subdomain` header to the Next.js app
#### Option A: Coolify Proxy Configuration (Recommended)
1. In your application, go to **Configuration****Domains**
2. Add both domains:
- `testbed.mk`
- `*.testbed.mk`
3. Coolify will request a wildcard SSL certificate. If your DNS provider supports DNS-01 challenges (Cloudflare, Route53, etc.), this works automatically. Otherwise, you may need to add each subdomain manually.
#### Option B: Custom Proxy Configuration
If Coolify doesn't support wildcard domains easily, add a **custom Caddy/Traefik configuration** in the Coolify settings:
For **Caddy** (Coolify's default proxy):
Create a file at `/data/coolify/proxy/caddy/custom/testbed.mk`:
```
*.testbed.mk {
reverse_proxy app:3000 {
header_up X-Subdomain {http.request.host.labels.2}
header_up X-Forwarded-Proto {scheme}
}
}
testbed.mk {
reverse_proxy app:3000 {
header_up X-Forwarded-Proto {scheme}
}
}
```
For **Traefik** (alternative proxy), you'd add labels to the container:
```yaml
traefik.http.routers.spomeniqr.rule: HostRegexp(`{subdomain:[a-z0-9-]+}.testbed.mk`) || Host(`testbed.mk`)
traefik.http.middlewares.spomeniqr-subdomain.headers.customrequestheaders.X-Subdomain:
```
> **Note:** The proxy configuration varies based on your Coolify version and proxy choice. Check the Coolify docs for the latest instructions on wildcard domains.
## Step 6: Configure Clerk
1. Go to [Clerk Dashboard](https://dashboard.clerk.com)
2. Switch to your **Production** instance
3. Under **Paths**, set:
- Sign-in: `/sign-in`
- Sign-up: `/sign-up`
4. Under **Domains**, add:
- `testbed.mk`
- `*.testbed.mk`
## Step 7: Configure Contabo S3
1. Log into Contabo Object Storage
2. Create a bucket named `monuments-images` in region `eu-2`
3. Set the **bucket policy** for public read:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": { "AWS": ["*"] },
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::monuments-images/*"]
}
]
}
```
4. Set **CORS** to allow uploads:
```json
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST"],
"AllowedOrigins": ["https://testbed.mk", "https://*.testbed.mk"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]
```
5. Create an API key with read/write permissions
## Step 8: Deploy
1. Click **Deploy** in the Coolify dashboard
2. Watch the build logs — it should:
- Install dependencies
- Run `npx prisma generate`
- Build the Next.js app
- Run `npx prisma migrate deploy`
- Start the server on port 3000
3. Once deployed, visit `https://testbed.mk` to verify
## Step 9: Verify Subdomain Routing
Test that wildcard subdomains work:
1. Create a memorial page with subdomain `test-memorial`
2. Visit `https://test-memorial.testbed.mk`
3. Check browser DevTools network tab — the `X-Subdomain` header should be set by the proxy
4. The Next.js middleware reads `X-Subdomain` and rewrites to the correct page
If subdomains aren't working:
- Check DNS: `dig *.testbed.mk +short` should return your VPS IP
- Check Coolify proxy logs for the wildcard domain configuration
- Verify the `X-Subdomain` header is being set in the proxy config
## Step 10: Verify Everything
```bash
# App health
curl -s https://testbed.mk | head -5
# Subdomain routing
curl -sI https://test-memorial.testbed.mk | head -5
# API health
curl -s https://testbed.mk/api/check-subdomain?slug=test | python3 -m json.tool
# Database connection (from Coolify terminal)
docker exec spomeniqr-app npx prisma db push --accept-data-loss
```
## Troubleshooting
### Build fails with Prisma errors
Make sure `DATABASE_URL` points to the Coolify internal hostname (e.g., `spomeniqr-db:5432`), not `localhost`. The app and database must be on the same Coolify network.
### Images return 401 from S3
Make sure you've applied the bucket policy for public read. See Step 7. If Contabo doesn't serve public objects via URL, the app uses an `/api/image?key=...` proxy route as a fallback.
### Subdomain routing not working
- Verify DNS wildcard `*.testbed.mk` points to your VPS
- Check that Coolify's proxy config includes both `testbed.mk` and `*.testbed.mk`
- Check the proxy access logs — the `X-Subdomain` header should appear
- If using a custom Caddyfile, make sure it's in the right directory and reload the proxy
### Clerk authentication issues
- Ensure production Clerk keys are set (not `pk_test_` / `sk_test_`)
- Verify `testbed.mk` and `*.testbed.mk` are added in Clerk dashboard domains
- Check that `NEXT_PUBLIC_APP_URL` and `NEXT_PUBLIC_APP_DOMAIN` are set correctly
### Database migration issues
If migrations fail on deploy, you can run them manually from the Coolify terminal:
```bash
# SSH into the app container
docker exec -it spomeniqr-app sh
# Run migrations
npx prisma migrate deploy
# Or push schema changes directly
npx prisma db push
```
## Environment Variables Reference
| Variable | Required | Description |
|----------|----------|-------------|
| `DATABASE_URL` | Yes | PostgreSQL connection string (Coolify internal) |
| `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` | Yes | Clerk publishable key (`pk_live_...`) |
| `CLERK_SECRET_KEY` | Yes | Clerk secret key (`sk_live_...`) |
| `NEXT_PUBLIC_CLERK_SIGN_IN_URL` | Yes | `/sign-in` |
| `NEXT_PUBLIC_CLERK_SIGN_UP_URL` | Yes | `/sign-up` |
| `S3_ENDPOINT` | Yes | Contabo S3 endpoint URL |
| `S3_REGION` | Yes | Contabo S3 region (e.g. `eu-2`) |
| `S3_ACCESS_KEY_ID` | Yes | S3 access key |
| `S3_SECRET_ACCESS_KEY` | Yes | S3 secret key |
| `S3_BUCKET_NAME` | Yes | S3 bucket name |
| `NEXT_PUBLIC_APP_URL` | Yes | `https://testbed.mk` |
| `NEXT_PUBLIC_APP_DOMAIN` | Yes | `testbed.mk` |
| `ADMIN_SESSION_SECRET` | Yes | Secret signing admin session cookies (openssl rand -hex 32) |
| `SUPER_ADMIN_USERNAME` | No | Super-admin username (default `super`) |
| `SUPER_ADMIN_PASSWORD_HASH` | No | Super-admin bcrypt hash; if unset, super login is unavailable |
| `NODE_ENV` | Yes | `production` |
## Useful Coolify Commands
- **Redeploy:** Project → Application → Deploy
- **View Logs:** Project → Application → Logs
- **SSH into container:** Project → Application → Terminal
- **Database Management:** Project → Database → Admin (pgAdmin or Prisma Studio)
- **SSL Certificates:** Managed automatically by Coolify for configured domains
## Updates
To update the application:
1. Push changes to your Git repository
2. Coolify will auto-deploy if **Auto Deploy** is enabled, or click **Deploy** manually
3. The `start.sh` script runs `npx prisma migrate deploy` on every startup, so schema changes are applied automatically