334 lines
11 KiB
Markdown
334 lines
11 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:** **Nixpacks** (auto-detected) or **Docker** (uses the Dockerfile)
|
|
|
|
### Option A: Nixpacks (Recommended — simpler)
|
|
|
|
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
|
|
```
|
|
|
|
### Option B: Docker (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 migration and standalone server startup.
|
|
|
|
**Note:** If using Docker build pack, the `DATABASE_URL` must use `spomeniqr-db` as the host (Coolify internal network), not `localhost`.
|
|
|
|
## 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
|
|
|
|
# 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.
|
|
|
|
## 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` |
|
|
| `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 |