Skip to main content

Sandbox Environment

The Platform-Kernel sandbox is a Docker Compose overlay on top of the full infrastructure stack. It adds three things the bare stack doesn't have:

  1. Seed data injected into PostgreSQL at first boot (test tenant, users, roles, wallets)
  2. Envoy proxy in front of all Go services as the single ingress point (localhost:8080)
  3. Extended start periods for health checks to accommodate slow first-boot migrations
note

Sandbox = production stack + seed data. There is no "mock" infrastructure. Every API call you make in sandbox hits real Kafka, real ClickHouse, real Vault. This ensures module behaviour is identical in dev and prod.


Architecture


Starting the Sandbox

The sandbox uses the docker-compose.sandbox.yml overlay. Run both files together from .dev/kernel/:

docker compose \
--env-file docker/versions.env \
-f docker/docker-compose.yml \
-f docker/docker-compose.sandbox.yml \
up --build -d
important

First-boot takes 3–5 minutes. ClamAV downloads ~300MB of antivirus signatures. PostgreSQL runs the seed script. ClickHouse initializes encrypted volumes. Subsequent restarts are under 30 seconds.

Monitor progress:

docker compose ps
docker compose logs -f --tail=50

Wait until Envoy is healthy:

curl -sf http://localhost:9901/ready
# LIVE

What's Pre-seeded

The file docker/sandbox/seed.sql is injected into PostgreSQL by the Docker entrypoint on first run. It creates a predictable set of test data:

Test Tenant

FieldValue
ID00000000-0000-7000-0000-000000000001
NameSandbox Corp
Statusactive
Planpro (unlimited dev limits)

Test Users

EmailPasswordRole
[email protected]Sandbox2026!Tenant Admin (all permissions)
[email protected]Sandbox2026!Standard User (read-only)
[email protected]Sandbox2026!Developer (module install)

Pre-created Resources

  • 2 wallets (USD, EUR) for the admin user with test balances
  • 3 pre-registered roles: tenant_admin, developer, viewer
  • Feature flag sandbox_mode set to true for the test tenant
  • 5 sample audit records across different resource types
tip

Seed is applied once on first PostgreSQL data-volume initialisation. To re-seed (reset all data), stop the stack, remove the volume, and restart:

docker compose down -v
docker compose --env-file docker/versions.env \
-f docker/docker-compose.yml \
-f docker/docker-compose.sandbox.yml \
up --build -d

Service Endpoints in Sandbox

When running the sandbox overlay, all HTTP traffic goes through Envoy on port 8080. Direct service ports remain bound for observability but you should not use them for API calls.

ServiceSandbox Endpoint
REST API (all primitives)http://localhost:8080/api/v1/
WebSocket (Notify)ws://localhost:8080/ws
Envoy Admin UIhttp://localhost:9901
RabbitMQ Managementhttp://localhost:15672 (kernel / rabbitmq_dev_password)
ClickHouse HTTPhttp://localhost:8123
Vault UIhttp://localhost:8200 (token: kernel-dev-root-token)
GoFeatureFlag APIhttp://localhost:1031
SeaweedFS S3http://localhost:8333

Authenticating in Sandbox

Obtain a JWT for the pre-seeded admin user:

curl -s -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "Sandbox2026!"
}' | jq .

Response:

{
"access_token": "<JWT — valid 15 minutes>",
"refresh_token": "<opaque token — valid 7 days>",
"token_type": "Bearer",
"expires_in": 900
}

Export the token for subsequent requests:

export TOKEN="<access_token from above>"

Verify your identity:

curl -s http://localhost:8080/api/v1/auth/me \
-H "Authorization: Bearer $TOKEN" | jq .

Connecting Your Module to the Sandbox

Initialize the SDK in your module pointing to the local sandbox Gateway:

import { kernel } from '@platform/sdk-core';

kernel.init({
gateway: 'http://localhost:8080',
token: process.env.PLATFORM_ACCESS_TOKEN, // from env or auth flow
});

Then use any primitive normally — it hits the real sandbox services:

// Create a record in your module's autoApi model
const contact = await kernel.data().create('contacts', {
first_name: 'Jane',
last_name: 'Doe',
});

// Publish an event
await kernel.events().publish('crm.contact.created', {
contactId: contact.id,
tenantId: contact.tenant_id,
});

// Check feature flag
const isFeatureEnabled = await kernel.flags().isEnabled('sandbox_mode');

Verifying Sandbox Health

Run all five checks to confirm the sandbox is fully operational:

# 1. Gateway liveness
curl -sf http://localhost:8080/live # 200 OK

# 2. Gateway full readiness (checks all downstream services)
curl -sf http://localhost:8080/health | jq .status
# "ok"

# 3. IAM: login with seed user
curl -sf -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"Sandbox2026!"}' \
| jq '.token_type'
# "Bearer"

# 4. Kafka broker is reachable
docker exec platform-kafka \
/opt/kafka/bin/kafka-broker-api-versions.sh \
--bootstrap-server localhost:9092 2>&1 | grep "broker version"

# 5. ClickHouse is responding (used by Audit + Analytics)
curl -sf "http://localhost:8123/?query=SELECT%201"
# 1

Sandbox vs Full Stack Differences

AspectFull Stack (docker-compose.yml)Sandbox Overlay (+ docker-compose.sandbox.yml)
Entry pointIndividual service portsEnvoy on :8080 (single ingress)
Seed dataNone (empty databases)Pre-seeded tenant, users, wallets, roles
Health check timingStandard (20–30 s start period)Extended (60–90 s, accounts for seed execution)
Use caseCI/CD integration testsModule development, manual testing

Stopping the Sandbox

# Stop without removing data volumes (fastest restart)
docker compose -f docker/docker-compose.yml \
-f docker/docker-compose.sandbox.yml \
down

# Stop AND remove all data (full reset)
docker compose -f docker/docker-compose.yml \
-f docker/docker-compose.sandbox.yml \
down -v