run

Docker to Run 2.0 Migration Guide

Run 2.0 is experimental and opt-in. Use run v2 to access the v2 commands.

This guide helps you migrate from Docker/Docker Compose to Run 2.0.

Table of Contents

  1. Why Migrate?
  2. Migration Strategy
  3. Step-by-Step Migration
  4. Command Mapping
  5. Architecture Changes
  6. Common Patterns
  7. Troubleshooting

Why Migrate?

Metric Docker Run 2.0 Improvement
Startup time 5-10s <10ms 500-1000x
Image size 50-500MB <5MB 10-100x
Memory usage 256MB+ <10MB 25x+
Build time 1-5min <10s 6-30x
Cold start 500ms+ <10ms 50x+

Additional Benefits:

Migration Strategy

Keep Docker for stateful services (databases, caches), migrate application logic to WASI.

Docker Compose -> Run 2.0 Hybrid
App (Docker) -> App (WASI) <10ms
DB (Docker) -> DB (Docker) Keep
Cache -> Cache Keep

Phase 2: Pure WASI (Goal)

Migrate everything to WASI components.

Run 2.0 Hybrid -> Run 2.0 Pure
App (WASI) -> App (WASI)
DB (Docker) -> DB (WASI) All WASI
Cache -> Cache

Step-by-Step Migration

Step 1: Analyze Your Docker Setup

# Install Run 2.0
curl -sSL https://run.esubalew.et/install.sh | bash

# Analyze compose file
cd your-project
run v2 compose analyze docker-compose.yml

Output:

Services:
  OK web-api   -> WASI component (can migrate)
  OK worker    -> WASI component (can migrate)
  WARN postgres -> Docker bridge (stateful, keep Docker)
  WARN redis    -> Docker bridge (stateful, keep Docker)

Recommendation: Hybrid mode (2 WASI + 2 Docker)
Estimated startup: 5s (Docker) + 10ms (WASI)
Estimated size: 128MB (Docker) + 3MB (WASI)

Step 2: Auto-Generate run.toml

run v2 compose migrate docker-compose.yml run.toml

This creates a run.toml with:

Before: docker-compose.yml

version: '3'
services:
  web:
    build: .
    ports:
      - "8080:8080"
    environment:
      DATABASE_URL: postgres://db:5432/mydb
    depends_on:
      - db
  
  db:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret

After: run.toml

[package]
name = "my-app"
version = "1.0.0"

[[component]]
name = "web"
source = "src/lib.rs"
language = "rust"
wit = "wit/http.wit"

[component.web.env]
DATABASE_URL = "postgres://localhost:5432/mydb"

[bridge.postgres]
image = "postgres:15"
ports = { 5432 = 5432 }
env = { POSTGRES_PASSWORD = "secret" }

Step 3: Convert Application to WASI Component

Option A: Rust

# Install cargo-component
cargo install cargo-component

# Initialize component
cargo component new web --lib

# Write code
cat > src/lib.rs <<'EOF'
wit_bindgen::generate!({
    world: "http-handler",
    exports: {
        "wasi:http/handler": Handler,
    },
});

struct Handler;

impl exports::wasi::http::handler::Guest for Handler {
    fn handle(request: Request) -> Response {
        Response::new(200, b"Hello from Run 2.0!")
    }
}
EOF

# Build
cargo component build --release

Option B: Python

# Install componentize-py
pip install componentize-py

# Write code
cat > app.py <<'EOF'
def handle_request(request):
    return {
        "status": 200,
        "body": b"Hello from Run 2.0!"
    }
EOF

# Build
componentize-py -d http.wit -o app.wasm app.py

Option C: TypeScript

# Install jco
npm install -g @bytecodealliance/jco

# Write code
cat > index.ts <<'EOF'
export function handleRequest(request: Request): Response {
    return new Response("Hello from Run 2.0!", { status: 200 });
}
EOF

# Build
jco transpile index.ts -o app.wasm

Step 4: Test Hybrid Setup

# Start dev server
run v2 dev

# Output:
[run] Starting WASI components...
[web] OK Built in 8ms
[run] Starting Docker bridge...
[docker] postgres -> postgres:15
[run] All services ready:
[run]   http://localhost:8080 (web)

# Test
curl http://localhost:8080
# Hello from Run 2.0!

Step 5: Deploy

# Production build
run v2 build --release --reproducible

# Deploy (examples)
run v2 deploy --target local
run v2 deploy --target edge --provider cloudflare
run v2 deploy --target registry

Command Mapping

Docker Command Run 2.0 Equivalent Notes
docker-compose up run v2 dev <10ms startup for WASI
docker-compose build run v2 build <10s builds
docker-compose down run stop Stops all components
docker-compose logs run v2 dev (shows logs) Unified logs
docker-compose ps run v2 info Component status
docker-compose exec run v2 exec Execute in component
docker build run v2 build No Dockerfile needed
docker run run v2 exec Direct execution
docker push run v2 deploy --target registry WASI registry
docker pull run v2 install Download components

Architecture Changes

Before: Docker Compose

docker-compose.yml
|-- Service A (container)
|   |-- Dockerfile
|   |-- node_modules/
|   `-- app.js
|-- Service B (container)
|   |-- Dockerfile
|   |-- venv/
|   `-- app.py
`-- Database (container)
    `-- postgres:15

Docker Daemon (required)
|-- Network bridge
|-- Volume management
`-- Container orchestration

Issues:

After: Run 2.0

run.toml
|-- Component A (WASI)    <10ms, 2MB
|-- Component B (WASI)    <10ms, 3MB
`-- Database (Docker*)    5s, 128MB

Run Runtime (no daemon)
|-- Component Model (WASI 0.2)
|-- Capability security
`-- Direct host process

*Docker bridge optional

Benefits:

Common Patterns

Pattern 1: HTTP Service

Before (Dockerfile):

FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "server.js"]

After (run.toml):

[[component]]
name = "api"
source = "src/lib.rs"
language = "rust"
wit = "wit/http.wit"

Pattern 2: Database Connection

Before:

services:
  app:
    depends_on:
      - db
    environment:
      DATABASE_URL: postgres://db:5432/mydb
  db:
    image: postgres:15

After:

[[component]]
name = "app"
source = "src/lib.rs"
env = { DATABASE_URL = "postgres://localhost:5432/mydb" }

[bridge.postgres]
image = "postgres:15"
ports = { 5432 = 5432 }

Pattern 3: Multi-service Application

Before (docker-compose.yml):

services:
  frontend:
    build: ./frontend
    ports: ["3000:3000"]
  
  backend:
    build: ./backend
    ports: ["8080:8080"]
  
  worker:
    build: ./worker

After (run.toml):

[[component]]
name = "frontend"
source = "frontend/src/lib.rs"

[[component]]
name = "backend"
source = "backend/src/lib.rs"

[[component]]
name = "worker"
source = "worker/src/lib.rs"

All start in <10ms, no containers needed.

Pattern 4: Environment Variables

Before:

services:
  app:
    environment:
      NODE_ENV: production
      API_KEY: ${API_KEY}

After:

[[component]]
name = "app"
env = { NODE_ENV = "production", API_KEY = "${API_KEY}" }

[env.production]
NODE_ENV = "production"
LOG_LEVEL = "info"

Troubleshooting

Issue 1: “Docker is not available”

If you see this error but have Docker installed:

# Check Docker is running
docker info

# Enable Docker bridge in run.toml
[bridge]
enabled = true

Issue 2: Port conflicts

# Error: Port 8080 already in use

# Solution: Change port in run.toml
[[component]]
name = "web"
dev.port = 8081  # Changed from 8080

Issue 3: Missing dependencies

# Error: Cannot find module 'xyz'

# For Rust: Add to Cargo.toml
[dependencies]
xyz = "1.0"

# For Python: Add to requirements.txt
xyz==1.0.0

# For JS/TS: Add to package.json
{
  "dependencies": {
    "xyz": "^1.0.0"
  }
}

Issue 4: WASI compatibility

Not all code can run in WASI yet. Use Docker bridge for:

# Keep these in Docker bridge
[bridge.legacy-service]
image = "my-legacy-app"

Issue 5: Performance regression

If WASI is slower than Docker:

# Enable release builds
run v2 build --release

# Verbose logs
run v2 dev --verbose

# Check component size
ls -lh target/wasm/*.wasm

# Optimize
[component.my-component]
opt_level = 3
strip_debug = true

Migration Checklist

Success Stories

Case 1: Microservices API

Before:

After:

Result: 100x smaller, 1000x faster startup

Case 2: Serverless Edge

Before:

After:

Result: 66x smaller, 30x faster

Case 3: CI/CD Pipeline

Before:

After:

Result: 10x faster, verifiable

Next Steps

  1. Start with hybrid mode (WASI + Docker bridge)
  2. Gradually migrate stateful services to WASI
  3. Optimize component size and startup time
  4. Deploy to edge/serverless if applicable
  5. Remove Docker entirely when ready

Support