Dyrected
Deployment

Docker Deployment

Self-hosting Dyrected in a container.

Dyrected has no official pre-built Docker image. For self-hosted deployments you build your own image from your Next.js or Nuxt application — the Dyrected engine runs inside your app, not as a separate service.


Dockerfile (Next.js)

FROM node:20-alpine AS base

FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build

FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production

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

COPY --from=builder /app/public ./public
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"

CMD ["node", "server.js"]

This uses Next.js standalone output. Add output: 'standalone' to your next.config.ts.


Docker Compose

A complete self-hosted setup with PostgreSQL and Redis:

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - '3000:3000'
    environment:
      NODE_ENV: production
      DATABASE_URL: postgresql://dyrected:password@db:5432/dyrected
      REDIS_URL: redis://redis:6379
      DYRECTED_JWT_SECRET: ${DYRECTED_JWT_SECRET}
      DYRECTED_API_KEY: ${DYRECTED_API_KEY}
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped
    volumes:
      - uploads:/app/public/uploads   # persist local file uploads

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: dyrected
      POSTGRES_USER: dyrected
      POSTGRES_PASSWORD: password
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U dyrected"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    volumes:
      - redisdata:/data
    restart: unless-stopped

volumes:
  pgdata:
  redisdata:
  uploads:

Store secrets in a .env file alongside docker-compose.yml:

# .env  (never commit this)
DYRECTED_JWT_SECRET=a-long-random-secret
DYRECTED_API_KEY=sk_live_...

Health check

Add a health check endpoint to your app and reference it in your orchestrator:

// app/api/health/route.ts  (Next.js)
export async function GET() {
  return Response.json({ ok: true })
}

Docker Compose health check:

app:
  healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 10s

Environment variables

VariableRequiredDescription
DATABASE_URLPostgreSQL connection string
DYRECTED_JWT_SECRETSecret used to sign JWTs. Use a long random string.
DYRECTED_API_KEYSite API key for server-to-server requests
REDIS_URLCloud modeRedis connection string (required for Cloud live preview token mode)
NODE_ENVSet to production to disable Ethereal email fallback and dev warnings

Persistent volumes

PathWhat it storesNotes
/app/public/uploadsFiles from LocalStorageAdapter adapterNot needed if using S3/Cloudinary
/var/lib/postgresql/dataPostgreSQL dataMount in the db service
/data (Redis)Redis AOF/RDB persistenceMount in the redis service

If you use LocalStorageAdapter for file uploads, the uploads volume must be mounted. For production, prefer an S3-compatible storage adapter — it works across multiple replicas and doesn't need a volume.

On this page