spomeni/coolify.md
2026-06-20 19:28:05 +02:00

11 KiB

Deploying SpomeniQR on Coolify

This guide covers deploying SpomeniQR to a VPS using Coolify — 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)
  • 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 ResourceDatabase
  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 ResourceApplication
  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)

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:

# 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 ConfigurationDomains
  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
  1. In your application, go to ConfigurationDomains
  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:

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
  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:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": { "AWS": ["*"] },
      "Action": ["s3:GetObject"],
      "Resource": ["arn:aws:s3:::monuments-images/*"]
    }
  ]
}
  1. Set CORS to allow uploads:
[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "PUT", "POST"],
    "AllowedOrigins": ["https://testbed.mk", "https://*.testbed.mk"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3600
  }
]
  1. 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

# 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:

# 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