Deploy Next.js Supabase with Docker

Deploy your MakerKit Next.js Supabase application using Docker. Covers Dockerfile generation, image building, container registry, and production deployment.

Deploy your MakerKit Next.js 16 application using Docker containers for full infrastructure control. This guide covers the standalone build output, multi-stage Dockerfiles, container registries, and production deployment with Docker Compose.

Overview

StepPurpose
Generate DockerfileCreate optimized Docker configuration
Configure environmentSet up production variables
Build imageCreate the container image
Push to registryUpload to DockerHub or GitHub Container Registry
DeployRun on your server or cloud platform

Prerequisites

Before starting:

  1. Docker installed locally
  2. Set up Supabase project
  3. Generate environment variables

Step 1: Generate the Dockerfile

MakerKit provides a generator that creates an optimized Dockerfile and configures Next.js for standalone output:

pnpm run turbo gen docker

This command:

  1. Creates a Dockerfile in the project root
  2. Sets output: "standalone" in next.config.mjs
  3. Installs platform-specific dependencies for Tailwind CSS

Step 2: Configure Environment Variables

Create the Production Environment File

Generate your environment variables:

pnpm turbo gen env

Copy the generated file to apps/web/.env.production.local.

Separate Build-Time and Runtime Secrets

Docker images should not contain secrets. Separate your variables into two groups:

Build-time variables (safe to include in image):

NEXT_PUBLIC_SITE_URL=https://yourdomain.com
NEXT_PUBLIC_PRODUCT_NAME=MyApp
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLIC_KEY=eyJ...
NEXT_PUBLIC_BILLING_PROVIDER=stripe
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...

Runtime secrets (add only when running container):

SUPABASE_SECRET_KEY=eyJ...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
SUPABASE_DB_WEBHOOK_SECRET=your-secret
RESEND_API_KEY=re_...
CAPTCHA_SECRET_TOKEN=...

Prepare for Build

Before building, temporarily remove secrets from your env file to avoid baking them into the image:

  1. Open apps/web/.env.production.local
  2. Comment out or remove these lines:
    # SUPABASE_SECRET_KEY=...
    # STRIPE_SECRET_KEY=...
    # STRIPE_WEBHOOK_SECRET=...
    # SUPABASE_DB_WEBHOOK_SECRET=...
    # RESEND_API_KEY=...
    # CAPTCHA_SECRET_TOKEN=...
  3. Save the file
  4. Keep the secrets somewhere safe for later

Step 3: Build the Docker Image

Build the image for your target architecture:

For AMD64 (most cloud servers)

docker buildx build --platform linux/amd64 -t myapp:latest .

For ARM64 (Apple Silicon, AWS Graviton)

docker buildx build --platform linux/arm64 -t myapp:latest .

Build Options

FlagPurpose
--platformTarget architecture
-tImage name and tag
--no-cacheForce fresh build
--progress=plainShow detailed build output

Build typically completes in 3-10 minutes depending on your machine.


Step 4: Add Runtime Secrets

After building, restore the secrets to your environment file:

SUPABASE_SECRET_KEY=eyJ...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
SUPABASE_DB_WEBHOOK_SECRET=your-secret
RESEND_API_KEY=re_...
CAPTCHA_SECRET_TOKEN=...

Step 5: Run the Container

Local Testing

Test the image locally:

docker run -d \
-p 3000:3000 \
--env-file apps/web/.env.production.local \
myapp:latest

Access your app at http://localhost:3000.

Run Options

FlagPurpose
-dRun in background (detached)
-p 3000:3000Map port 3000
--env-fileLoad environment variables from file
--name myappName the container
--restart unless-stoppedAuto-restart on failure

Step 6: Push to Container Registry

GitHub Container Registry

  1. Create a Personal Access Token with write:packages scope
  2. Login:
    docker login ghcr.io -u YOUR_USERNAME
  3. Tag your image:
    docker tag myapp:latest ghcr.io/YOUR_USERNAME/myapp:latest
  4. Push:
    docker push ghcr.io/YOUR_USERNAME/myapp:latest

DockerHub

  1. Login:
    docker login
  2. Tag your image:
    docker tag myapp:latest YOUR_USERNAME/myapp:latest
  3. Push:
    docker push YOUR_USERNAME/myapp:latest

Step 7: Deploy to Production

Pull and Run on Your Server

SSH into your server and run:

# Login to registry (GitHub example)
docker login ghcr.io
# Pull the image
docker pull ghcr.io/YOUR_USERNAME/myapp:latest
# Run the container
docker run -d \
-p 3000:3000 \
--env-file .env.production.local \
--name myapp \
--restart unless-stopped \
ghcr.io/YOUR_USERNAME/myapp:latest

Using Docker Compose

Create docker-compose.yml:

services:
web:
image: ghcr.io/YOUR_USERNAME/myapp:latest
ports:
- "3000:3000"
env_file:
- .env.production.local
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/healthcheck"]
interval: 30s
timeout: 10s
retries: 3

Run with:

docker compose up -d

CI/CD with GitHub Actions

Automate builds and deployments with GitHub Actions:

# .github/workflows/docker.yml
name: Build and Push Docker Image
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64
push: true
tags: ghcr.io/${{ github.repository }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max

Production Considerations

Health Checks

MakerKit includes a health check endpoint. Use it for monitoring:

curl http://localhost:3000/api/healthcheck

Resource Limits

Set memory and CPU limits in production:

docker run -d \
-p 3000:3000 \
--memory="512m" \
--cpus="1.0" \
--env-file .env.production.local \
myapp:latest

Logging

View container logs:

# Follow logs
docker logs -f myapp
# Last 100 lines
docker logs --tail 100 myapp

Troubleshooting

Build fails with memory error

Increase Docker's memory allocation or use a more powerful build machine:

docker build --memory=4g -t myapp:latest .

Container exits immediately

Check logs for errors:

docker logs myapp

Common causes:

  • Missing environment variables
  • Port already in use
  • Invalid configuration

Image too large

The standalone output mode creates smaller images. If still too large:

  1. Ensure you're using the generated Dockerfile (not a custom one)
  2. Check for unnecessary files in your project
  3. Use .dockerignore to exclude development files

Environment variables not working

  1. Verify the env file path is correct
  2. Check file permissions
  3. Ensure no syntax errors in the env file
  4. For NEXT_PUBLIC_ variables, rebuild the image (they're embedded at build time)

Next Steps