Cogitator
Deployment

Deploy Package

One-command deployment for Cogitator agents — Docker and Fly.io targets with auto-detection.

One-command deployment engine for Cogitator agents. Analyzes your project, generates a Dockerfile and target-specific artifacts, runs preflight checks and deploys to Docker or Fly.io.

Installation

pnpm add @cogitator-ai/deploy

Quick Start

Via CLI

cogitator deploy --dry-run            # analyze + preflight, change nothing
cogitator deploy                      # Docker: build image, start the compose stack
cogitator deploy --target fly         # Fly.io
cogitator deploy --registry ghcr.io/myorg --region fra
cogitator deploy --no-push            # build locally, skip the registry push
cogitator deploy status
cogitator deploy destroy

The CLI reads the deploy section of cogitator.yml (or the file passed with -c, --config), applies --registry / --region on top, and picks the target from --target, then deploy.target, then docker.

Programmatic API

import { Deployer } from '@cogitator-ai/deploy';

const deployer = new Deployer();

const plan = await deployer.plan({
  projectDir: process.cwd(),
  target: 'docker',
  noPush: true,
});

console.log(plan.config); // resolved DeployConfig (image, port, services, secrets, ...)
console.log(plan.warnings); // e.g. missing start script, local Ollama on a cloud target
console.log(plan.preflight.passed, plan.preflight.checks);

const result = await deployer.deploy({ projectDir: process.cwd(), target: 'fly' });
if (!result.success) throw new Error(result.error);
console.log(result.url); // https://my-app.fly.dev
console.log(result.endpoints); // { api, a2a, health }
MethodDescription
plan(options)Analyze + preflight. Returns config, preflight, warnings, analysis, provider
deploy(options)plan, the provider's generate(), then its deploy(). Returns DeployResult (dryRun stops before writing or deploying anything)
status(target, config, projectDir)DeployStatus (running, url, instances)
destroy(target, config, projectDir)Tears down the deployment; throws when it fails
availableTargets()Names of registered providers (['docker', 'fly'] by default)
getProvider(target)The registered DeployProvider; throws for an unknown target
registerProvider(provider)Add a custom DeployProvider

Configuration

Add a deploy section to cogitator.yml. Both the CLI and the programmatic Deployer read it from the project directory, and configOverrides win over it field by field (inside services, env, health and resources too):

deploy:
  target: fly # docker | fly
  server: express # express | fastify | hono | koa (detected from package.json when omitted)
  port: 3000 # default 3000
  image: my-agent # Docker image / Fly app name (default: package.json name)
  region: iad # Fly.io only (default iad)
  instances: 2 # Fly.io only; the docker target runs one container
  registry: ghcr.io/myorg
  services:
    redis: true
    postgres: true
  env:
    LOG_LEVEL: info
  secrets:
    - OPENAI_API_KEY
  health: # Dockerfile HEALTHCHECK and Fly.io HTTP check
    path: /cogitator/health
    interval: 30s
    timeout: 5s
  resources: # Fly.io VM size
    memory: 512mb
    cpu: 1

Explicit services and secrets replace auto-detection rather than merging with it.

health.path defaults to /cogitator/health, where the Express and Fastify adapters answer at their default base path. Set it only when the adapter is mounted elsewhere (Hono, Koa and Tetsu answer wherever you mount them). Projects from create-cogitator-app already set it: the api-server template mounts the adapter at /api and writes health.path: /api/health, and the nextjs template serves /api/health from its own route.

Secrets are read from the current environment or the project's .env file (the environment wins). They are checked during preflight, passed through to the Docker Compose stack, and imported into Fly.io with fly secrets import --stage. Multi-line secret values are rejected for Fly.io because fly secrets import cannot carry them.

Auto-Detection

WhatSourceExample
Server adapterpackage.json dependencies@cogitator-ai/express → Express
Image / app namepackage.json name@acme/My Agent → my-agent
Package managerLockfilepnpm-lock.yaml → pnpm install --frozen-lockfile, yarn.lock → yarn, package-lock.json → npm ci
Start commandscripts.start, then mainnode dist/index.js → CMD ["node","dist/index.js"], any other script → npm start; neither → node dist/server.js (src/server.js without TypeScript)
Build steptsconfig.json + scripts.buildMulti-stage build running <pm> run build
Servicescogitator.yml / .yaml memory adapteradapter: postgres → PostgreSQL service
Required secretsllm.defaultModel (+ defaultProvider)openai/gpt-6.1-sol → OPENAI_API_KEY; Bedrock → both AWS keys
Ollama CloudModel tag :cloud / -cloudgpt-oss:120b-cloud → OLLAMA_API_KEY

Problems found during analysis (invalid config, unparsable package.json, missing start script, tsconfig.json without a build script, local Ollama models on cloud targets, instances > 1 on Docker) are returned as plan.warnings instead of being ignored.

Deploy Targets

Docker

Artifacts are written to .cogitator/: Dockerfile (on node:22-alpine, multi-stage when the project has TypeScript, with a HEALTHCHECK on health.path), .dockerignore, Dockerfile.dockerignore and docker-compose.prod.yml. A root .dockerignore is created when the project has none, so .env and node_modules never enter the image.

deploy builds and tags the image (<registry>/<image>:latest), pushes it when a registry is configured, then runs docker compose up -d for the app plus the Redis (redis:7-alpine) and PostgreSQL (pgvector/pgvector:pg16) services it needs — health-gated, data in named volumes, with REDIS_URL / DATABASE_URL injected into the app. Each key appears once in the app's environment: a REDIS_URL / DATABASE_URL in env replaces the bundled service URL, and one listed in secrets is read from your environment with the bundled URL as fallback. The result URL is http://localhost:<port>. status reports whether the app service is running; destroy runs docker compose down and keeps volumes.

Preflight checks: Docker installed, daemon running, Compose v2 available, registry credentials (read from the Docker config, credential helpers and stores), secrets.

Fly.io

Generates .cogitator/fly.toml (your own fly.toml is never overwritten) with [env], HTTP checks from health, a shared-CPU VM sized from resources (default 256mb, 1 CPU), and min_machines_running from instances (machines auto-stop when instances is 1). deploy creates the app when needed, stages secrets, runs fly deploy --config .cogitator/fly.toml --dockerfile .cogitator/Dockerfile, and scales to instances. The result URL is https://<image>.fly.dev, with the A2A card at /.well-known/agent.json. Preflight checks that flyctl (or fly) is on PATH, that you are logged in (fly auth whoami), and the secrets.

Custom Providers

import {
  ArtifactGenerator,
  Deployer,
  ProjectAnalyzer,
  type DeployProvider,
} from '@cogitator-ai/deploy';
import type {
  DeployConfig,
  DeployResult,
  DeployStatus,
  GeneratedArtifacts,
  PreflightResult,
} from '@cogitator-ai/types';

class KubernetesProvider implements DeployProvider {
  readonly name = 'kubernetes';

  async preflight(config: DeployConfig, projectDir: string): Promise<PreflightResult> {
    return { checks: [], passed: true };
  }
  async generate(config: DeployConfig, projectDir: string): Promise<GeneratedArtifacts> {
    const build = new ProjectAnalyzer().detectBuild(projectDir);
    return new ArtifactGenerator().generate(config, build);
  }
  async deploy(
    config: DeployConfig,
    artifacts: GeneratedArtifacts,
    projectDir: string
  ): Promise<DeployResult> {
    return { success: true, url: 'https://my-agent.example.com' };
  }
  async status(config: DeployConfig, projectDir: string): Promise<DeployStatus> {
    return { running: true };
  }
  async destroy(config: DeployConfig, projectDir: string): Promise<void> {}
}

const deployer = new Deployer();
deployer.registerProvider(new KubernetesProvider());
await deployer.deploy({ projectDir: process.cwd(), target: 'kubernetes' });

Deployer.deploy() runs preflight, calls your provider's generate(config, projectDir) and passes the result to deploy(); with dryRun it stops after generate(). Writing the files and deploying them is up to the provider. The example reuses the built-in Dockerfile: ProjectAnalyzer.detectBuild(projectDir) reports the TypeScript setup, package manager, lockfile, build script and start command that ArtifactGenerator needs.

All external commands are executed without a shell, so paths with spaces and user-supplied values are passed verbatim.

Architecture

ProjectAnalyzer  →  DeployProvider.generate  →  DeployProvider.deploy  →  Result
(detect config)     (Dockerfile, etc.)            (docker/fly)              (url, status)

See Also

On this page