Skip to main content

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 handles build-time. You bundle with the CLI, deploy with Docker.

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

  • 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

┌─────────────────────────────────────────────────────────────────┐
│ 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

  • Docker installed (Get Docker)
  • (Optional) Pre-built flow bundle from the CLI

Quick start with demos

The Docker image includes demo bundles for instant testing.

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

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

The Docker container is configured via environment variables:

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 VariableRequiredDefaultDescription
BUNDLEYes-Path, URL, or piped content of a pre-built .mjs bundle
PORTNo8080Server port
HOSTNo0.0.0.0Bind address

Web flow

docker run -p 3000:3000 \
-v $(pwd)/dist:/app/static \
-e STATIC_DIR=/app/static \
-e PORT=3000 \
walkeros/flow:latest
Environment VariableRequiredDefaultDescription
STATIC_DIRNo/app/distDirectory to serve
PORTNo3000Server port
HOSTNo0.0.0.0Bind address

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

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

{
"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

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

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

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)

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

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

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)

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

Google Cloud Run

Cloud Run is ideal for serverless deployment of walkerOS flows.

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

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)

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

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

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

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

# 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

For better observability, use structured logging in your destinations:

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

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

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

Startup time

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

Resource usage

Typical resource consumption:

MetricIdleActive (1000 req/min)
Memory~50MB~100-150MB
CPU< 1%5-15%
Startup< 1s< 1s

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

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

# 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

# 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

# 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

💡 Need implementation support?
elbwalker offers hands-on support: setup review, measurement planning, destination mapping, and live troubleshooting. Book a 2-hour session (€399)