Skip to content

Docker Deployment Guide

Complete guide for building and deploying Next.js applications as Docker containers to the Kubernetes cluster.

Prerequisites: - Next.js application created and tested locally - Docker installed on host (Proxmox) - Access to internal container registry (10.89.97.201:30500) - Kubernetes cluster access

See Also: - Production App Deployment - Complete deployment workflow - Production Deployment - Kubernetes deployment patterns - App Conventions - Application structure standards


Next.js Configuration

Required for Docker deployment:

Update next.config.ts (or next.config.js) to enable standalone output:

import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  output: 'standalone',  // Required for Docker deployment
}

export default nextConfig

Why: Standalone mode creates a minimal production build with only required dependencies, significantly reducing Docker image size and startup time.

Dockerfile Structure

Standard multi-stage Dockerfile for Next.js + Supabase apps:

FROM node:20-alpine AS base

# Dependencies stage
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci

# Builder stage
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Build args for environment variables (CRITICAL for Supabase apps)
ARG NEXT_PUBLIC_SUPABASE_URL
ARG NEXT_PUBLIC_SUPABASE_ANON_KEY
ENV NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL}
ENV NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY}
ENV NEXT_TELEMETRY_DISABLED=1

RUN npm run build

# Runner stage
FROM base AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# Copy standalone build
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

# IMPORTANT: Always use JSON array syntax with quotes
CMD ["node", "server.js"]

Critical Dockerfile Requirements

1. Build-Time Environment Variables

Problem: Next.js apps with Supabase need environment variables during the build step for pre-rendering pages (SSG/ISR).

Solution: Use ARG and ENV directives in the builder stage:

ARG NEXT_PUBLIC_SUPABASE_URL
ARG NEXT_PUBLIC_SUPABASE_ANON_KEY
ENV NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL}
ENV NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY}

Build command:

docker build \
  --build-arg NEXT_PUBLIC_SUPABASE_URL=http://10.89.97.214:8000 \
  --build-arg NEXT_PUBLIC_SUPABASE_ANON_KEY="your-anon-key" \
  -t app-name:v1.0.0 .

2. CMD Syntax - JSON Array Format

CRITICAL: Always use JSON array syntax with proper quotes:

# ✅ Correct
CMD ["node", "server.js"]

# ❌ Wrong - will fail with "/bin/sh: [node,: not found"
CMD [node, server.js]

Why: Without quotes, Docker interprets the array as shell syntax, causing parsing errors.

3. .dockerignore

Required to prevent permission errors and reduce image size:

node_modules
.next
.git
.gitignore
.github
.env
.env*.local
supabase/.branches
supabase/.temp
.supabase
README.md

Multi-Schema Supabase Configuration

CRITICAL for production deployments:

When deploying to k8s with the shared Supabase instance, you must configure clients to use the correct schema:

// lib/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr'

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      db: { schema: "your_app_schema" }  // ← CRITICAL for multi-schema setups!
    }
  )
}

// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createClient() {
  const cookieStore = await cookies()

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      db: { schema: "your_app_schema" },  // ← CRITICAL for multi-schema setups!
      cookies: {
        get(name: string) {
          return cookieStore.get(name)?.value
        },
      },
    }
  )
}

Schema names: - home_portal - Home Portal - money_tracker - Money Tracker - rms - Recipe Management System

What happens without this: Client defaults to the first schema in PostgREST config (usually home_portal), causing 404 errors when querying tables.

See Supabase Multi-App Architecture for complete details.

Docker Build and Push

Standard workflow:

# Build with proper args
docker build \
  --build-arg NEXT_PUBLIC_SUPABASE_URL=http://10.89.97.214:8000 \
  --build-arg NEXT_PUBLIC_SUPABASE_ANON_KEY="$(kubectl get secret -n supabase supabase-secrets -o jsonpath='{.data.ANON_KEY}' | base64 -d)" \
  -t app-name:v1.0.0 .

# Tag for registry
docker tag app-name:v1.0.0 10.89.97.201:30500/app-name:v1.0.0
docker tag app-name:v1.0.0 10.89.97.201:30500/app-name:latest

# Push to registry
docker push 10.89.97.201:30500/app-name:v1.0.0
docker push 10.89.97.201:30500/app-name:latest

Insecure Registry Configuration

Required for internal registry access:

The k8s container registry (10.89.97.201:30500) runs without TLS. Docker must be configured to allow insecure registries.

Host Docker configuration (/etc/docker/daemon.json):

{
  "insecure-registries": ["10.89.97.201:30500"]
}

Apply changes:

systemctl restart docker

Common Docker Issues

BuildKit / Buildx Issues

Issue: npm ci fails with "spawn sh EACCES" during Docker build

npm error Error: spawn sh EACCES
npm error     at ChildProcess._handle.onexit (node:internal/child_process:285:19)
npm error   errno: -13,
npm error   code: 'EACCES',
npm error   syscall: 'spawn sh',

Root Cause: Corrupted or misconfigured Docker buildx builder. This can happen after system upgrades (apt dist-upgrade) that update Docker, containerd, or buildx packages.

Diagnosis:

# Check buildx builder status
docker buildx ls

# If you see "v0.0.0+unknown" for the default builder, it's corrupted:
# NAME/NODE     DRIVER/ENDPOINT   STATUS    BUILDKIT         PLATFORMS
# default*      docker
#  \_ default    \_ default       running   v0.0.0+unknown   linux/amd64...

Solution: Create a fresh buildx builder:

# Create new builder with proper BuildKit
docker buildx create --name fresh-builder --use

# Bootstrap it (downloads BuildKit image)
docker buildx inspect --bootstrap

# Verify it shows a proper version (e.g., v0.26.2)
docker buildx ls
# NAME/NODE            DRIVER/ENDPOINT                   STATUS    BUILDKIT   PLATFORMS
# fresh-builder*       docker-container
#  \_ fresh-builder0    \_ unix:///var/run/docker.sock   running   v0.26.2    linux/amd64...

Permanent Fix for Deploy Scripts:

Update deploy scripts to explicitly use the working builder:

# Instead of:
docker build -t app:v1.0.0 .

# Use:
docker buildx build --builder fresh-builder --load -t app:v1.0.0 .

Why this happens: - System upgrades can leave the default Docker builder in a broken state - The default docker driver doesn't properly initialize BuildKit after upgrades - Using docker-container driver (via docker buildx create) runs BuildKit in a container, bypassing host issues

Prevention: - After major system upgrades, verify docker buildx ls shows valid BuildKit versions - Consider using the docker-container driver by default for more isolation


Issue: Build fails with missing Supabase URL/key

Error: @supabase/ssr: Your project's URL and API key are required
Solution: Pass build args (see Build-Time Environment Variables above)

Issue: Container starts then immediately crashes

/bin/sh: [node,: not found
Solution: Fix CMD syntax to use JSON array with quotes: CMD ["node", "server.js"]

Issue: Permission denied errors during build

Error: EACCES: permission denied, scandir '/app/node_modules'
Solution: Add node_modules, .next, and .git to .dockerignore

Issue: Docker push fails - HTTPS to HTTP error

http: server gave HTTP response to HTTPS client
Solution: Configure insecure registry in Docker daemon.json (see above)

See Troubleshooting for more issues and solutions.


Automated Deployment Scripts

For complete deployment automation including build, push, and Kubernetes deployment, see:

  • Production App Deployment - Complete workflow
  • /root/tower-fleet/scripts/deploy-app.sh - Automated deployment script
  • /root/tower-fleet/scripts/rebuild-app.sh - Rebuild and redeploy script

See Also