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.

Docker File Path
Specify which Docker configuration file to use:
docker-compose.yml # Default
docker/preview.yml # Custom location
Dockerfile # Single containerThe 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:
| Name | Default Port | Result |
|---|---|---|
PORT | 3000 | PREVIEW_PORT_PORT=5000 (allocated) |
API_PORT | 4000 | PREVIEW_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/previewThese 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.shOption 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_successfullyOption 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
docker-compose.yml (Recommended)
Use PREVIEW_PORT_{NAME} environment variables for dynamic port allocation:
services:
app:
build: .
ports:
- "${PREVIEW_PORT_APP:-3000}:3000"
environment:
- PORT=3000Multi-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: previewDockerfile 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:
app→PREVIEW_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 } // UnclearSet 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 1Requirements
- Docker: Must be installed and running
- Docker Compose v2: Uses
docker compose(notdocker-compose)
# Install Docker (macOS)
brew install --cask docker
# Verify installation
docker --version
docker compose version