> Part of the walkerOS documentation. Project overview and full index: <https://www.walkeros.io/llms.txt>

# walkerOS Docker

The walkerOS Docker image (`walkeros/flow`) is a **pure runtime container** for executing pre-built flow bundles in production. It's designed for fast startup (< 1 second), minimal footprint (\~150-200MB), and cloud-native deployment.

**Key philosophy**: Docker handles runtime, [CLI](https://www.walkeros.io/docs/apps/cli.md) handles build-time. You bundle with the CLI, deploy with Docker.

## What it does[​](#what-it-does "Direct link to What it does")

* ✅ **Executes pre-built `.mjs` bundles** from the CLI
* ✅ **Runs collection servers** (HTTP endpoints for event ingestion)
* ✅ **Serves static files** (web tracking scripts)
* ✅ **Sub-1 second startup** - No npm downloads, no build steps
* ✅ **Production-ready** - Health checks, non-root user, signal handling

## What it doesn't do[​](#what-it-doesnt-do "Direct link to What it doesn't do")

* ❌ **No bundling** - Flows must be pre-built with the CLI
* ❌ **No package downloads** - All dependencies must be bundled
* ❌ **No configuration generation** - Configuration comes from your bundle

Think of it as the **deployment target**, not the build tool.

## Build → deploy workflow[​](#build--deploy-workflow "Direct link to Build → deploy workflow")

```
┌─────────────────────────────────────────────────────────────────┐

│                       DEVELOPMENT                                │

├─────────────────────────────────────────────────────────────────┤

│                                                                  │

│   flow.json ──► walkeros bundle ──► flow.mjs                    │

│      │              (CLI)              │                         │

│      │                                 │                         │

│   Config as code              Pre-built bundle                   │

│   (sources, destinations)     (all deps included)                │

│                                                                  │

└────────────────────────────────┬────────────────────────────────┘

                                 │

                                 ▼

┌─────────────────────────────────────────────────────────────────┐

│                       PRODUCTION                                 │

├─────────────────────────────────────────────────────────────────┤

│                                                                  │

│   flow.mjs ──► Docker Container ──► HTTP Server                 │

│      │           (runtime only)        │                         │

│      │                                 │                         │

│   Mount or bake in         < 1 second startup                   │

│                            /health, /collect                     │

│                                                                  │

└─────────────────────────────────────────────────────────────────┘
```

This separation means:

* **CLI** handles complexity (npm resolution, bundling, TypeScript)
* **Docker** stays simple (just runs JavaScript)
* **Startup is instant** (no npm install, no build step)

## Prerequisites[​](#prerequisites "Direct link to Prerequisites")

* Docker installed ([Get Docker](https://docs.docker.com/get-docker/))
* (Optional) Pre-built flow bundle from the [CLI](https://www.walkeros.io/docs/apps/cli.md)

## Quick start with demos[​](#quick-start-with-demos "Direct link to Quick start with demos")

The Docker image includes demo bundles for instant testing.

### Demo: event collection[​](#demo-event-collection "Direct link to Demo: event collection")

```
docker run -p 8080:8080 \
  -e FLOW=/app/demos/demo-collect.mjs \
  walkeros/flow:latest
```

Output:

```
✓ Loading flow: /app/demos/demo-collect.mjs

✓ Starting collection server on port 8080

✓ Health check available at /health



[Demo Output] page view

[Demo Output] product add
```

The demo is self-contained and generates test events automatically.

### Demo: static file serving[​](#demo-static-file-serving "Direct link to Demo: static file serving")

```
docker run -p 3000:3000 \
  -e PORT=3000 \
  -e STATIC_DIR=/app/demos \
  walkeros/flow:latest
```

Visit `http://localhost:3000/demo-serve.js` to download the demo web bundle.

## Configuration[​](#configuration "Direct link to Configuration")

The Docker container is configured via environment variables:

### Server flow[​](#server-flow "Direct link to Server flow")

```
docker run -p 8080:8080 \
  -v $(pwd)/flow.mjs:/app/flow.mjs \
  -e FLOW=/app/flow.mjs \
  -e PORT=8080 \
  -e HOST=0.0.0.0 \
  walkeros/flow:latest
```

| Environment Variable | Required | Default   | Description                                              |
| -------------------- | -------- | --------- | -------------------------------------------------------- |
| `BUNDLE`             | Yes      | -         | Path, URL, or piped content of a pre-built `.mjs` bundle |
| `PORT`               | No       | `8080`    | Server port                                              |
| `HOST`               | No       | `0.0.0.0` | Bind address                                             |

### Web flow[​](#web-flow "Direct link to Web flow")

```
docker run -p 3000:3000 \
  -v $(pwd)/dist:/app/static \
  -e STATIC_DIR=/app/static \
  -e PORT=3000 \
  walkeros/flow:latest
```

| Environment Variable | Required | Default     | Description        |
| -------------------- | -------- | ----------- | ------------------ |
| `STATIC_DIR`         | No       | `/app/dist` | Directory to serve |
| `PORT`               | No       | `3000`      | Server port        |
| `HOST`               | No       | `0.0.0.0`   | Bind address       |

### Bundle input methods[​](#bundle-input-methods "Direct link to Bundle input methods")

The runtime accepts bundles from three sources (checked in priority order):

**File path** (default): mount or bake the bundle into the container:

```
docker run -p 8080:8080 \
  -v $(pwd)/flow.mjs:/app/flow/bundle.mjs \
  walkeros/flow:latest
```

**URL**: fetch the bundle from a remote location at startup:

```
docker run -p 8080:8080 \
  -e BUNDLE=https://cdn.example.com/flow.mjs \
  walkeros/flow:latest
```

Works with presigned S3/GCS URLs, CDNs, or any HTTP(S) endpoint.

**Stdin pipe**: pipe the bundle directly into the container:

```
cat flow.mjs | docker run -i -p 8080:8080 walkeros/flow:latest
```

Stdin takes highest priority. If data is piped, the `BUNDLE` env var is ignored.

## Including files[​](#including-files "Direct link to Including files")

The `include` field in your flow config copies folders into the bundle output directory at build time. When combined with `--dockerfile`, these folders are automatically added as `COPY` instructions in the generated Dockerfile.

### The `include` field[​](#the-include-field "Direct link to the-include-field")

```
{
  "version": 4,
  "include": ["./public"],
  "flows": {
    "default": {
      "config": { "platform": "server" },
      "stores": {
        "files": {
          "package": "@walkeros/server-store-fs",
          "config": { "settings": { "basePath": "./public" } }
        }
      },
      "sources": {
        "http": {
          "package": "@walkeros/server-source-express",
          "next": [
            {
              "match": { "key": "ingest.method", "operator": "eq", "value": "GET" },
              "next": "file"
            }
          ]
        }
      },
      "transformers": {
        "file": {
          "package": "@walkeros/server-transformer-file",
          "config": { "settings": { "prefix": "/static" } },
          "env": { "store": "$store.files" }
        }
      }
    }
  }
}
```

Avoid circular includes

The `include` field copies folders into the **output directory** (where the bundle is written). If your output is `dist/bundle.mjs` and you set `include: ["./dist"]`, the bundler will try to copy `dist/` into `dist/dist/`, a circular operation that will error.

**Fix:** Only include folders that are **not** the output directory:

* `"include": ["./shared"]`: copies `shared/` into the output directory
* `"include": ["./credentials"]`: copies `credentials/` into the output directory

Build with Dockerfile generation:

```
walkeros bundle flow.json -o dist/ --dockerfile
```

The generated `dist/Dockerfile` includes COPY lines for both the bundle and the included folder:

```
FROM walkeros/flow:latest
COPY bundle.mjs /app/flow/bundle.mjs
COPY public/ /app/flow/public/
ENV BUNDLE=/app/flow/bundle.mjs
EXPOSE 8080
```

### Volume mount for development[​](#volume-mount-for-development "Direct link to Volume mount for development")

During development, mount folders directly instead of baking them into the image:

```
docker run -v ./public:/app/flow/public -p 8080:8080 walkeros/flow
```

### Directory separation[​](#directory-separation "Direct link to Directory separation")

Keep served files and credentials in separate directories:

* **`public/`**: Files served to clients (static assets, templates)
* **`credentials/`**: Secret files (API keys, service accounts)

The fs store only serves files under its configured `basePath`. Never point it at a directory containing secrets.

## Deployment workflows[​](#deployment-workflows "Direct link to Deployment workflows")

### Workflow 1: volume mount (development)[​](#workflow-1-volume-mount-development "Direct link to Workflow 1: volume mount (development)")

Mount pre-built bundles directly into the container.

**1. Build with CLI**

```
walkeros bundle server-collect.json
```

This creates `dist/flow.mjs`.

**2. Run with Docker**

```
docker run -d \
  -p 8080:8080 \
  -v $(pwd)/dist/flow.mjs:/app/flow.mjs \
  -e FLOW=/app/flow.mjs \
  --name walkeros-flow \
  walkeros/flow:latest
```

**3. Test it**

```
curl -X POST http://localhost:8080/collect \
  -H "Content-Type: application/json" \
  -d '{"name":"page view","data":{"title":"Home"}}'
```

**4. View logs**

```
docker logs -f walkeros-flow
```

### Workflow 2: custom Docker image (production)[​](#workflow-2-custom-docker-image-production "Direct link to Workflow 2: custom Docker image (production)")

Build a custom Docker image with your bundle baked in.

**1. Build with CLI and generate Dockerfile**

```
walkeros bundle production.json --dockerfile
```

This creates `dist/bundle.mjs` and `dist/Dockerfile`.

**2. Build image**

```
cd dist
docker build -t my-analytics:v1.0.0 .
```

**3. Run it**

```
docker run -d -p 8080:8080 my-analytics:v1.0.0
```

**4. Push to registry**

```
docker tag my-analytics:v1.0.0 gcr.io/my-project/analytics:v1.0.0
docker push gcr.io/my-project/analytics:v1.0.0
```

### Workflow 3: multi-stage build[​](#workflow-3-multi-stage-build "Direct link to Workflow 3: multi-stage build")

Build and bundle in a single Dockerfile.

```
# Build stage
FROM node:18-alpine AS builder
WORKDIR /build
RUN npm install -g @walkeros/cli
COPY flow.json .
RUN walkeros bundle flow.json --output flow.mjs

# Runtime stage
FROM walkeros/flow:latest
COPY --from=builder /build/flow.mjs /app/flow.mjs

ENV BUNDLE=/app/flow.mjs
EXPOSE 8080
```

Build and run:

```
docker build -t analytics-collector .
docker run -d -p 8080:8080 analytics-collector
```

## Docker Compose[​](#docker-compose "Direct link to Docker Compose")

### Single service[​](#single-service "Direct link to Single service")

```
version: '3.8'

services:
  collector:
    image: walkeros/flow:latest
    environment:
      FLOW: /app/flow.mjs
      PORT: 8080
    volumes:
      - ./dist/flow.mjs:/app/flow.mjs
    ports:
      - "8080:8080"
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 3s
      retries: 3
```

Run it:

```
docker-compose up -d
docker-compose logs -f
```

### Multiple services (web + server)[​](#multiple-services-web--server "Direct link to Multiple services (web + server)")

```
version: '3.8'

services:
  # Server-side event collection
  collector:
    image: walkeros/flow:latest
    environment:
      FLOW: /app/server-flow.mjs
    volumes:
      - ./dist/server-flow.mjs:/app/server-flow.mjs
    ports:
      - "8080:8080"
    restart: unless-stopped

  # Web bundle serving
  tracker:
    image: walkeros/flow:latest
    environment:
      STATIC_DIR: /app/static
      PORT: 3000
    volumes:
      - ./dist:/app/static
    ports:
      - "3000:3000"
    restart: unless-stopped
```

## Cloud deployment[​](#cloud-deployment "Direct link to Cloud deployment")

### Google Cloud Run[​](#google-cloud-run "Direct link to Google Cloud Run")

Cloud Run is ideal for serverless deployment of walkerOS flows.

#### Deploy collection endpoint[​](#deploy-collection-endpoint "Direct link to Deploy collection endpoint")

**1. Create server flow**

Create `bigquery-collect.json`:

```
{
  "version": 4,
  "flows": {
    "default": {
      "config": {
        "platform": "server",
        "bundle": {
          "packages": {
            "@walkeros/collector": { "version": "latest" },
            "@walkeros/server-source-express": { "version": "latest" },
            "@walkeros/server-destination-gcp": { "version": "latest" },
            "@walkeros/destination-demo": { "version": "latest" }
          }
        }
      },
      "sources": {
        "http": {
          "package": "@walkeros/server-source-express",
          "config": {
            "settings": {
              "path": "/collect",
              "port": 8080,
              "cors": true
            }
          }
        }
      },
      "destinations": {
        "bigquery": {
          "package": "@walkeros/server-destination-gcp",
          "config": {
            "settings": {
              "projectId": "my-project",
              "datasetId": "analytics",
              "tableId": "events"
            }
          }
        },
        "console": {
          "package": "@walkeros/destination-demo",
          "config": {
            "settings": { "name": "Logger" }
          }
        }
      },
      "collector": { "run": true }
    }
  }
}
```

Starting with Console

The config above includes **both** BigQuery and console destinations. This is a best practice:

* Console logs help debug issues in Cloud Run logs
* Start with console only, verify events flow correctly
* Then uncomment BigQuery destination for production

To start console-only: remove the `bigquery` destination block.

**2. Bundle the flow**

```
walkeros bundle bigquery-collect.json --output flow.mjs
```

**3. Create Dockerfile**

```
FROM walkeros/flow:latest
COPY flow.mjs /app/flow.mjs
ENV BUNDLE=/app/flow.mjs
ENV PORT=8080
```

**4. Build and push to Google Container Registry**

```
# Set your GCP project
export PROJECT_ID=my-project
export SERVICE_NAME=analytics-collector
export REGION=us-central1

# Build and push
docker build -t gcr.io/$PROJECT_ID/$SERVICE_NAME .
docker push gcr.io/$PROJECT_ID/$SERVICE_NAME
```

**5. Deploy to Cloud Run**

```
gcloud run deploy $SERVICE_NAME \
  --image gcr.io/$PROJECT_ID/$SERVICE_NAME \
  --platform managed \
  --region $REGION \
  --allow-unauthenticated \
  --port 8080 \
  --memory 512Mi \
  --cpu 1 \
  --max-instances 10 \
  --timeout 60s
```

**6. Get the URL**

```
gcloud run services describe $SERVICE_NAME \
  --platform managed \
  --region $REGION \
  --format 'value(status.url)'

# Output: https://analytics-collector-xxxxx-uc.a.run.app
```

**7. Test it**

```
curl -X POST https://analytics-collector-xxxxx-uc.a.run.app/collect \
  -H "Content-Type: application/json" \
  -d '{
    "name": "page view",
    "data": {
      "title": "Landing Page",
      "path": "/landing"
    },
    "user": {
      "id": "user123"
    }
  }'
```

**8. View logs**

```
gcloud run logs read $SERVICE_NAME \
  --region $REGION \
  --limit 50
```

You should see console logs showing the received events.

#### Deploy web bundle server[​](#deploy-web-bundle-server "Direct link to Deploy web bundle server")

**1. Create web flow**

Create `web-tracker.json`:

```
{
  "version": 4,
  "flows": {
    "default": {
      "config": {
        "platform": "web",
        "bundle": {
          "packages": {
            "@walkeros/collector": { "version": "latest" },
            "@walkeros/web-source-browser": { "version": "latest" },
            "@walkeros/web-destination-api": { "version": "latest" }
          }
        }
      },
      "sources": {
        "browser": {
          "package": "@walkeros/web-source-browser",
          "config": {
            "settings": {
              "pageview": true,
              "elb": "track"
            }
          }
        }
      },
      "destinations": {
        "api": {
          "package": "@walkeros/web-destination-api",
          "config": {
            "settings": {
              "url": "https://analytics-collector-xxxxx-uc.a.run.app/collect",
              "method": "POST"
            }
          }
        }
      },
      "collector": { "run": true }
    }
  }
}
```

**2. Bundle**

```
walkeros bundle web-tracker.json
```

This creates `tracker.js`.

**3. Create Dockerfile for serving**

```
FROM walkeros/flow:latest
COPY tracker.js /app/static/tracker.js
ENV STATIC_DIR=/app/static
ENV PORT=8080
```

**4. Deploy to Cloud Run**

```
# Build and push
docker build -t gcr.io/$PROJECT_ID/tracker-server .
docker push gcr.io/$PROJECT_ID/tracker-server

# Deploy
gcloud run deploy tracker-server \
  --image gcr.io/$PROJECT_ID/tracker-server \
  --platform managed \
  --region $REGION \
  --allow-unauthenticated \
  --port 8080
```

**5. Use in your website**

```
<script src="https://tracker-server-xxxxx-uc.a.run.app/tracker.js"></script>
<script>
  // Track custom events
  track('button click', { label: 'signup' });
</script>
```

Production Considerations

For production, consider:

* **Use a CDN**: Serve static files from Cloud Storage + Cloud CDN instead of Cloud Run
* **Custom domain**: Map your own domain (e.g., `cdn.example.com/tracker.js`)
* **Versioning**: Include version in filename (e.g., `tracker.v1.2.3.js`)
* **Caching**: Set appropriate cache headers

### AWS (ECS/Fargate)[​](#aws-ecsfargate "Direct link to AWS (ECS/Fargate)")

Similar workflow for AWS:

```
# Push to ECR
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin $AWS_ACCOUNT.dkr.ecr.us-east-1.amazonaws.com

docker tag my-analytics-collector:latest $AWS_ACCOUNT.dkr.ecr.us-east-1.amazonaws.com/analytics-collector:latest

docker push $AWS_ACCOUNT.dkr.ecr.us-east-1.amazonaws.com/analytics-collector:latest

# Create task definition and service using ECS console or CLI
# Configure port 8080, environment variables, and health check
```

### Kubernetes[​](#kubernetes "Direct link to Kubernetes")

Deploy to any Kubernetes cluster:

```
apiVersion: apps/v1
kind: Deployment
metadata:
  name: walkeros-flow
spec:
  replicas: 3
  selector:
    matchLabels:
      app: walkeros-flow
  template:
    metadata:
      labels:
        app: walkeros-flow
    spec:
      containers:
      - name: collector
        image: gcr.io/my-project/analytics-collector:v1.0.0
        ports:
        - containerPort: 8080
        env:
        - name: FLOW
          value: "/app/flow.mjs"
        - name: PORT
          value: "8080"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 30
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
  name: walkeros-flow
spec:
  selector:
    app: walkeros-flow
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: LoadBalancer
```

Apply:

```
kubectl apply -f deployment.yaml
kubectl get services walkeros-flow
```

## Health checks[​](#health-checks "Direct link to Health checks")

The Docker container provides a `/health` endpoint:

```
curl http://localhost:8080/health
```

Response:

```
{
  "status": "ok",
  "timestamp": 1701234567890,
  "file": "/app/flow.mjs"
}
```

Use this for:

* Docker health checks
* Kubernetes liveness/readiness probes
* Load balancer health checks
* Monitoring systems

## Monitoring and logs[​](#monitoring-and-logs "Direct link to Monitoring and logs")

### View Docker logs[​](#view-docker-logs "Direct link to View Docker logs")

```
# Follow logs
docker logs -f walkeros-flow

# Last 100 lines
docker logs --tail 100 walkeros-flow

# With timestamps
docker logs -t walkeros-flow
```

### Cloud Run logs[​](#cloud-run-logs "Direct link to Cloud Run logs")

```
# Recent logs
gcloud run logs read $SERVICE_NAME --region $REGION --limit 50

# Tail logs
gcloud run logs tail $SERVICE_NAME --region $REGION

# Filter by severity
gcloud run logs read $SERVICE_NAME --region $REGION --log-filter="severity>=ERROR"
```

### Structured logging[​](#structured-logging "Direct link to Structured logging")

For better observability, use structured logging in your destinations:

```
{
  "destinations": {
    "structured-logger": {
      "package": "@walkeros/destination-demo",
      "config": {
        "settings": {
          "format": "json"
        }
      }
    }
  }
}
```

## Security[​](#security "Direct link to Security")

The Docker image follows security best practices:

* ✅ **Non-root user** - Runs as `walker` (UID 1001)
* ✅ **Minimal base** - Alpine Linux (\~150-200MB)
* ✅ **No build tools** - Production dependencies only
* ✅ **Signal handling** - Graceful shutdown with Tini
* ✅ **Health checks** - Built-in endpoint for orchestrators

### Environment secrets[​](#environment-secrets "Direct link to Environment secrets")

For sensitive configuration (API keys, credentials), use environment variables:

```
# Docker
docker run -e BIGQUERY_KEY="$(cat key.json)" walkeros/flow

# Cloud Run
gcloud run deploy $SERVICE_NAME \
  --set-secrets="BIGQUERY_KEY=bigquery-key:latest"

# Kubernetes
kubectl create secret generic analytics-secrets \
  --from-file=bigquery-key=key.json
```

Reference secrets in your flow configuration using environment variable substitution.

## Performance[​](#performance "Direct link to Performance")

### Startup time[​](#startup-time "Direct link to Startup time")

* **Cold start**: < 1 second (flow already bundled)
* **Warm start**: < 100ms (container reuse)

### Resource usage[​](#resource-usage "Direct link to Resource usage")

Typical resource consumption:

| Metric  | Idle   | Active (1000 req/min) |
| ------- | ------ | --------------------- |
| Memory  | \~50MB | \~100-150MB           |
| CPU     | < 1%   | 5-15%                 |
| Startup | < 1s   | < 1s                  |

### Scaling[​](#scaling "Direct link to Scaling")

The container is designed for horizontal scaling:

```
# Docker Swarm
docker service scale walkeros-flow=5

# Kubernetes
kubectl scale deployment walkeros-flow --replicas=5

# Cloud Run (auto-scaling)
gcloud run services update $SERVICE_NAME --max-instances=100
```

## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting")

### Container won't start[​](#container-wont-start "Direct link to Container won't start")

```
# Check logs
docker logs walkeros-flow

# Common issues:
# - FLOW path doesn't exist
# - Port already in use
```

### Events not being received[​](#events-not-being-received "Direct link to Events not being received")

```
# Verify container is running
docker ps | grep walkeros

# Check health endpoint
curl http://localhost:8080/health

# Test event submission
curl -X POST http://localhost:8080/collect \
  -H "Content-Type: application/json" \
  -d '{"name":"test event","data":{}}'

# Check logs for errors
docker logs walkeros-flow
```

### Port conflicts[​](#port-conflicts "Direct link to Port conflicts")

```
# Use a different host port
docker run -p 8081:8080 walkeros/flow

# Or find and kill the conflicting process
lsof -ti:8080 | xargs kill
```

### Bundle not found[​](#bundle-not-found "Direct link to Bundle not found")

```
# Verify volume mount
docker run -v $(pwd)/dist/flow.mjs:/app/flow.mjs walkeros/flow

# Check if file exists in container
docker run --rm -it walkeros/flow ls -la /app/

# Verify FLOW environment variable
docker run -e FLOW=/app/flow.mjs walkeros/flow
```

## Next steps[​](#next-steps "Direct link to Next steps")

* **[CLI](https://www.walkeros.io/docs/apps/cli.md)** - Learn how to build flows
* **[Flow Configuration](https://www.walkeros.io/docs/getting-started/modes/bundled.md)** - Understand flow structure
* **[Sources](https://www.walkeros.io/docs/sources.md)** - Configure event sources
* **[Destinations](https://www.walkeros.io/docs/destinations.md)** - Set up analytics destinations
* **[Docker Hub](https://hub.docker.com/r/walkeros/flow)** - Official Docker images
