Production Deployment

Deploy an ElsiumAI app to production with proper security, observability, and scaling. This guide assumes you already have a working app (see Getting Started).

Prerequisites

  • Node.js 20+ (LTS recommended)
  • npm, pnpm, or bun for dependency management
  • A deployed database or vector store if using RAG
  • API keys for at least one LLM provider

Environment Configuration

ElsiumAI reads provider credentials from environment variables. Set these for each provider you use:

# Required — at least one provider key
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
GOOGLE_API_KEY=AIza...

# Optional — observability and app config
ELSIUM_LOG_LEVEL=info          # debug | info | warn | error
ELSIUM_ENV=production          # development | production
PORT=3000

Never commit API keys to source control. Use a secrets manager (AWS Secrets Manager, Vault, Doppler) or your platform’s environment variable configuration.

Docker Deployment

Use a multi-stage build to keep the production image small:

# Stage 1 — install and build
FROM oven/bun:1-alpine AS builder
WORKDIR /app

COPY package.json bun.lock ./
RUN bun install --frozen-lockfile

COPY . .
RUN bun run build

# Stage 2 — production image
FROM node:20-alpine AS runner
WORKDIR /app

RUN addgroup -S elsium && adduser -S elsium -G elsium

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

USER elsium
EXPOSE 3000

ENV NODE_ENV=production
ENV ELSIUM_ENV=production

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

Build and run:

docker build -t elsium-app .
docker run -p 3000:3000 \
  -e ANTHROPIC_API_KEY=sk-ant-... \
  -e ELSIUM_LOG_LEVEL=info \
  elsium-app

Production Configuration

Configure createApp with authentication, CORS, rate limiting, and observability:

import { createApp } from "@elsium-ai/app";

const app = createApp({
  gateway: {
    providers: {
      anthropic: { apiKey: process.env.ANTHROPIC_API_KEY! },
      openai: { apiKey: process.env.OPENAI_API_KEY! },
    },
    strategy: "fallback",
  },
  observe: {
    costTracking: true,
    tracing: true,
  },
  server: {
    cors: {
      origin: ["https://your-app.com"],
      methods: ["GET", "POST"],
    },
    auth: {
      type: "bearer",
      token: process.env.API_TOKEN!,
    },
    rateLimit: {
      windowMs: 60_000,
      maxRequests: 100,
    },
  },
});

app.listen(Number(process.env.PORT) || 3000);

Health Checks and Monitoring

The @elsium-ai/app package exposes health and metrics endpoints by default:

// GET /health — returns 200 if the service is running
// Response: { status: "ok", uptime: 12345, version: "0.3.0" }

// GET /metrics — returns cost and request metrics
// Response: { requests: 1024, errors: 3, totalCost: 4.82, ... }

Point your load balancer or container orchestrator health check at /health. Use /metrics to feed dashboards (Grafana, Datadog, etc.) via a scraper or push gateway.

Security Checklist

Before going live, verify every item:

  • API keys in environment variables — never hardcoded, never in source control
  • CORS restricted to known origins — no wildcard * in production
  • Rate limiting enabled — protect against abuse and runaway costs
  • Authentication configured — bearer tokens or API key auth on all endpoints
  • Content security — enable PII detection middleware if handling user data
  • Unicode normalization — applied automatically by the gateway security layer
  • SSRF protection — URL validation enabled for any tool that fetches external resources
  • Budget limits set — per-user and per-agent budgets to prevent cost overruns

Scaling Considerations

ElsiumAI is designed to be stateless at the application layer:

  • Horizontal scaling — run multiple instances behind a load balancer. No sticky sessions required.
  • External vector stores — for RAG, use a managed vector database (Pinecone, Weaviate, pgvector) rather than in-memory stores.
  • Agent memory — use shared storage or a custom external store adapter for conversation history if scaling beyond a single instance. The built-in SQLite store is best for single-instance persistence.
  • Connection pooling — the gateway manages provider connections internally. Each instance maintains its own pool.
// Example: persistent memory for a single instance
import {
  createSqliteMemoryStore,
  defineAgent,
  type AgentDependencies,
} from "@elsium-ai/agents";
import { gateway } from "@elsium-ai/gateway";

const llm = gateway({
  provider: "anthropic",
  apiKey: process.env.ANTHROPIC_API_KEY!,
});

const deps: AgentDependencies = {
  complete: (request) => llm.complete(request),
  stream: (request) => llm.stream(request),
};

const memoryStore = createSqliteMemoryStore({
  path: process.env.MEMORY_DB_PATH ?? "./data/memory.db",
});

const agent = defineAgent(
  {
    name: "support",
    system: "You help users with production support questions.",
    memory: {
      strategy: "sliding-window",
      store: memoryStore,
      agentId: "support",
      maxMessages: 50,
    },
  },
  deps,
);

Graceful Shutdown

Use createShutdownManager to ensure in-flight requests complete before the process exits:

import { createShutdownManager } from "@elsium-ai/core";

const shutdown = createShutdownManager({
  drainTimeoutMs: 15_000,
  onDrainStart: () => console.log("Draining in-flight requests"),
  onDrainComplete: () => console.log("All in-flight requests completed"),
  onForceShutdown: () =>
    console.warn("Shutdown timed out before all requests completed"),
});

async function handleRequest() {
  return "ok";
}

const response = await shutdown.trackOperation(handleRequest);
await shutdown.shutdown();
shutdown.dispose();

In Docker or Kubernetes, make sure the STOPSIGNAL is SIGTERM (the default) and the termination grace period is longer than drainTimeoutMs.

Logging Configuration

In production, use structured JSON logging for machine-parseable output:

import { createLogger, type LogLevel } from "@elsium-ai/core";

const level = (process.env.ELSIUM_LOG_LEVEL ?? "info") as LogLevel;

const log = createLogger({ level, pretty: false });

// Output: {"level":"info","message":"Server started","timestamp":"2026-03-04T...","data":{"port":3000}}
log.info("Server started", { port: 3000 });

Pipe JSON logs to your log aggregator (CloudWatch, Loki, Datadog Logs). Use child loggers to attach request-scoped context like trace IDs:

import { createLogger } from "@elsium-ai/core";

const log = createLogger();
const req = {
  headers: { "x-trace-id": "trace-123" },
  url: "/chat",
};

const reqLog = log.child({ traceId: req.headers["x-trace-id"] });
reqLog.info("Processing request", { path: req.url });

Set level: 'warn' in production to reduce noise, or level: 'debug' temporarily when investigating issues.