Arcwayv0.3.0

Deployment

Production configuration, Docker, PM2, reverse proxy, and CLI reference

This guide covers running Arcway in production.

Quick Start

# Build pages for production
npx arcway build

# Start the production server
npx arcway start

arcway start sets NODE_ENV=production, runs pending migrations, and starts the HTTP server with graceful shutdown handlers.

Environment Variables

Arcway automatically loads .env files in this order (later files override earlier ones):

  1. .env
  2. .env.local
  3. .env.{mode} (e.g., .env.production)
  4. .env.{mode}.local (e.g., .env.production.local)

Common environment variables:

# Required — vault master secret (sessions, JWT, callbacks, etc. derive from it)
# Generate with: npx arcway vault generate-key
ARCWAY_MASTER_SECRET=v1:…

# Database (PostgreSQL example)
DATABASE_URL=postgresql://user:pass@host:5432/mydb

# Email
SMTP_HOST=smtp.example.com
SMTP_USER=user@example.com
SMTP_PASS=your-smtp-password

# File storage (S3)
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=your-secret-key

# Encryption (if using vault encrypt/decrypt)
ENCRYPTION_KEY=your-encryption-key

Production Configuration

Dev vs Production Differences

AspectDevelopmentProduction
Session cookiessecure: false (HTTP)secure: true (HTTPS only)
LoggingPretty-printed with colorsJSON lines for machine parsing
File watchingEnabled, auto-restartsDisabled
Shutdown timeout5 seconds10 seconds
MigrationsAuto-run on bootAuto-run on boot

Server Configuration

// arcway.config.js
export default {
  server: {
    host: '0.0.0.0',                // Default: all interfaces
    port: 3000,                      // Default: 3000
    maxBodySize: 25 * 1024 * 1024,   // Default: 25MB
    shutdownTimeoutMs: 10_000,       // Default: 10 seconds
  },
};

Database

SQLite works for small deployments. For production, use PostgreSQL or MySQL:

// arcway.config.js
export default {
  database: {
    client: 'pg',
    connection: process.env.DATABASE_URL,
    pool: { min: 2, max: 10 },
  },
};

Install the database driver:

npm install pg       # PostgreSQL
npm install mysql2   # MySQL

Sessions

Session cookies are automatically set to secure: true in production, which requires HTTPS. Make sure your reverse proxy terminates TLS:

export default {
  // The session signing key is derived from vault.masterSecret — no password here.
  vault: {
    masterSecret: process.env.ARCWAY_MASTER_SECRET,
  },
  session: {
    cookieName: 'myapp.session',
    ttl: 86_400,                          // 1 day in seconds
    // secure defaults to true in production
  },
};

Cache and Queue

For multi-server deployments, switch from in-memory to Redis:

export default {
  cache: {
    driver: 'redis',
    redis: { host: 'localhost', port: 6379 },
  },
  queue: {
    driver: 'redis',
    redis: { host: 'localhost', port: 6379 },
  },
  events: {
    driver: 'redis',
    // Uses redis config from cache section
  },
};

File Storage

Switch from local storage to S3 for production:

export default {
  files: {
    driver: 's3',
    s3: {
      bucket: 'my-bucket',
      region: 'us-east-1',
      credentials: {
        accessKeyId: process.env.AWS_ACCESS_KEY_ID,
        secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
      },
    },
  },
};

Migrations

Migrations run automatically on boot — both arcway dev and arcway start. This is safe because Knex's migration system is idempotent (it tracks which migrations have already run).

Manual migration commands:

# Create a new migration
npx arcway migrate make add-posts-table

# Run pending migrations
npx arcway migrate run

# Rollback the last batch
npx arcway migrate rollback

Health Check

Arcway provides a built-in health endpoint at /_system/health:

curl http://localhost:3000/_system/health

Response (200 OK):

{
  "status": "ok",
  "components": {
    "database": { "status": "ok", "responseMs": 2 },
    "redis": { "status": "ok", "responseMs": 1 }
  }
}

Response (503 Service Unavailable):

{
  "status": "degraded",
  "components": {
    "database": { "status": "error", "responseMs": 5001, "error": "Connection timeout" },
    "redis": { "status": "ok", "responseMs": 1 }
  }
}

Use this endpoint with load balancers, Docker health checks, or monitoring tools.

Graceful Shutdown

Arcway handles SIGTERM and SIGINT signals for graceful shutdown. The shutdown sequence:

  1. Stop accepting new connections
  2. Stop job runner (halt background jobs)
  3. Close pages router
  4. Close API router
  5. Drain existing HTTP connections
  6. Disconnect events bus
  7. Disconnect Redis (if configured)
  8. Close database connection
  9. Exit

If shutdown takes longer than shutdownTimeoutMs (default: 10 seconds), the process force-exits with a warning.

Running with PM2

PM2 is a process manager that handles restarts, clustering, and log management:

npm install -g pm2

# Start
pm2 start "npx arcway start" --name myapp

# Or with an ecosystem file:
// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'myapp',
    script: 'npx',
    args: 'arcway start',
    env: {
      NODE_ENV: 'production',
      PORT: 3000,
    },
    instances: 1,          // Arcway handles its own jobs, use 1 instance
    autorestart: true,
    max_memory_restart: '512M',
  }],
};
pm2 start ecosystem.config.js
pm2 save    # Save process list for auto-restart on reboot
pm2 startup # Generate startup script

Docker

Dockerfile

FROM node:22-alpine

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci --omit=dev

COPY . .

# Build pages
RUN npx arcway build

EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD wget -qO- http://localhost:3000/_system/health || exit 1

CMD ["npx", "arcway", "start"]

docker-compose.yml

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - ARCWAY_MASTER_SECRET=${ARCWAY_MASTER_SECRET}
      - DATABASE_URL=postgresql://postgres:password@db:5432/myapp
    depends_on:
      db:
        condition: service_healthy

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

volumes:
  pgdata:

Reverse Proxy

Arcway should run behind a reverse proxy (nginx, Caddy, Traefik) that handles TLS termination.

Nginx

server {
    listen 443 ssl;
    server_name myapp.example.com;

    ssl_certificate /etc/letsencrypt/live/myapp.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/myapp.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket support
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Traefik (Docker labels)

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.myapp.rule=Host(`myapp.example.com`)"
  - "traefik.http.routers.myapp.entryPoints=websecure"
  - "traefik.http.routers.myapp.tls.certResolver=letsencrypt"
  - "traefik.http.services.myapp.loadbalancer.server.port=3000"

CLI Reference

CommandDescription
arcway devStart development server with file watching
arcway startStart production server
arcway buildBuild pages for production
arcway migrate make <name>Create a new migration file
arcway migrate runRun pending migrations
arcway migrate rollbackRollback last migration batch
arcway seedRun database seed files
arcway testRun test suite
arcway schemaSchema commands
arcway graphql-schemaGenerate GraphQL schema

On this page