agentcmd
ReferenceWorkflow Steps

Preview Step

Start Docker preview containers to test and review your application.

Overview

Launch Docker-based preview environments directly from your workflow. Preview containers provide isolated, production-like environments for testing AI-generated code changes.

Use for: Testing feature branches, reviewing PR changes, running integration tests in isolated environments

Configuration

interface PreviewStepConfig {
  /** Port mapping: key = env var name, value = container port */
  ports?: Record<string, number>;
  /** Environment variables for the container */
  env?: Record<string, string>;
  /** Custom Docker file path (overrides auto-detection) */
  dockerFilePath?: string;
  /** Memory limit (e.g., "1g", "512m") */
  maxMemory?: string;
  /** CPU limit (e.g., "1.0", "0.5") */
  maxCpus?: string;
}

Timeout: 5 minutes (300,000ms)

Result

interface PreviewStepResult {
  data: {
    containerId: string;
    status: string;  // "running" | "stopped" | "failed" | "skipped"
    urls: Record<string, string>;  // { app: "http://localhost:5000" }
  };
  success: boolean;
  error?: string;
  trace: TraceEntry[];
}

Basic Usage

Simple Preview

// Uses project's docker-compose.yml with default settings
const preview = await step.preview("deploy");

console.log(preview.data.urls);
// { app: "http://localhost:5000" }

Custom Port Configuration

const preview = await step.preview("deploy", {
  ports: { PORT: 3000, VITE_PORT: 5173 },
});

// Container receives env vars:
// PREVIEW_PORT_PORT=5000 (allocated)
// PREVIEW_PORT_VITE_PORT=5001 (allocated)

With Environment Variables

const preview = await step.preview("staging", {
  env: {
    NODE_ENV: "preview",
    API_URL: "https://api.staging.example.com",
  },
});

Custom Docker File

const preview = await step.preview("deploy", {
  dockerFilePath: "docker/compose-preview.yml",
});

Resource Limits

const preview = await step.preview("deploy", {
  maxMemory: "2g",
  maxCpus: "2.0",
});

Project Settings

Configure preview containers globally for your project via Project → Edit.

Project Settings

Docker File Path

Specify which Docker configuration file to use:

docker-compose.yml          # Default
docker/preview.yml          # Custom location
Dockerfile                  # Single container

The path is relative to your project root. This setting is used when no dockerFilePath is specified in the workflow step.

Port Mappings

Map environment variable names to container ports. AgentCmd allocates available host ports and injects them into your container:

NameDefault PortResult
PORT3000PREVIEW_PORT_PORT=5000 (allocated)
API_PORT4000PREVIEW_PORT_API_PORT=5001 (allocated)

Your docker-compose.yml uses these via environment variables:

services:
  app:
    ports:
      - "${PREVIEW_PORT_PORT:-3000}:3000"

Environment Variables

Set environment variables for the preview container (one per line, KEY=value format):

NODE_ENV=preview
API_KEY=test
DATABASE_URL=postgres://localhost/preview

These are passed to all services in your Docker Compose setup.

Seed Data

To populate your preview environment with test data, use a startup script in your Dockerfile or docker-compose.yml:

Option 1: Dockerfile entrypoint

# Dockerfile
COPY scripts/seed.sh /docker-entrypoint.d/
RUN chmod +x /docker-entrypoint.d/seed.sh

Option 2: Docker Compose command

services:
  app:
    command: sh -c "pnpm db:seed && pnpm start"

Option 3: Init container pattern

services:
  seed:
    build: .
    command: pnpm db:seed
    depends_on:
      db:
        condition: service_healthy

  app:
    build: .
    depends_on:
      seed:
        condition: service_completed_successfully

Option 4: Environment-based seeding

// In your app startup
if (process.env.NODE_ENV === 'preview') {
  await seedDatabase();
}

Set NODE_ENV=preview in Project Settings → Environment Variables.

Docker Configuration

Use PREVIEW_PORT_{NAME} environment variables for dynamic port allocation:

services:
  app:
    build: .
    ports:
      - "${PREVIEW_PORT_APP:-3000}:3000"
    environment:
      - PORT=3000

Multi-Service Setup

services:
  server:
    build: .
    ports:
      - "${PREVIEW_PORT_SERVER:-4100}:4100"
    command: pnpm dev:server

  client:
    build: .
    ports:
      - "${PREVIEW_PORT_CLIENT:-4101}:4101"
    command: pnpm dev:client

  db:
    image: postgres:15
    ports:
      - "${PREVIEW_PORT_DB:-5432}:5432"
    environment:
      POSTGRES_PASSWORD: preview

Dockerfile Only

If no docker-compose.yml exists, a Dockerfile is used:

FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
ENV PORT=3000
EXPOSE 3000
CMD ["node", "dist/index.js"]

Common Patterns

Agent → Build → Preview

// AI implements feature
await step.agent("implement", {
  agent: "claude",
  prompt: "Implement user authentication",
});

// Build the application
await step.cli("build", { command: "pnpm build" });

// Start preview
const preview = await step.preview("deploy");

// Create annotation with preview URL
await step.annotation("preview-ready", {
  message: `Preview available at ${preview.data.urls.app}`,
});

Preview with Worktree Isolation

// Create isolated worktree
const worktree = await step.git("setup", {
  operation: "worktree-add",
  projectPath,
  branch: "feat/my-feature",
  worktreeName: `run-${runId}`,
});

// Work in isolated environment
await step.agent("implement", {
  agent: "claude",
  prompt: "Implement feature",
  workingDir: worktree.data.worktreePath,
});

// Preview the isolated changes
const preview = await step.preview("deploy");

Graceful Docker Unavailability

When Docker is not available, the step succeeds with empty URLs:

const preview = await step.preview("deploy");

if (Object.keys(preview.data.urls).length === 0) {
  // Docker unavailable - skip preview-dependent steps
  await step.annotation("no-preview", {
    message: "Preview skipped - Docker not available",
  });
} else {
  // Preview running
  await step.annotation("preview-ready", {
    message: `Preview at ${preview.data.urls.app}`,
  });
}

Port Allocation

  • Ports are allocated from range 5000-5999
  • Each container gets unique ports to avoid conflicts
  • Port names become uppercase env vars: appPREVIEW_PORT_APP
  • Multiple previews can run simultaneously

Best Practices

Use descriptive port names:

ports: { SERVER: 4100, CLIENT: 4101 }  // Clear intent
// vs
ports: { PORT1: 4100, PORT2: 4101 }    // Unclear

Set resource limits for resource-intensive apps:

{
  maxMemory: "2g",
  maxCpus: "1.0",
}

Include health checks in your Dockerfile:

HEALTHCHECK --interval=30s --timeout=3s \
  CMD curl -f http://localhost:3000/health || exit 1

Requirements

  • Docker: Must be installed and running
  • Docker Compose v2: Uses docker compose (not docker-compose)
# Install Docker (macOS)
brew install --cask docker

# Verify installation
docker --version
docker compose version

Next Steps