Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 54 additions & 37 deletions internal/localmode/assets/docker-compose.template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ services:
AWS_REGION: us-east-1
AWS_ACCESS_KEY_ID: local
AWS_SECRET_ACCESS_KEY: local
REDIS_TIMEOUT: "60"
USAGE_SYNC_INTERVAL: "30"
USAGE_SYNC_LOCK_TTL: "30"
REDIS_TIMEOUT: "60s"
USAGE_SYNC_INTERVAL: "30s"
USAGE_SYNC_LOCK_TTL: "30s"
SOURCE_ARCHIVE_SIZE_LIMIT_MB: "256"
LAMBDA_TARGET_CONTAINER_SIZE_LIMIT_MB: "4096"
PORT: "8000"
Expand All @@ -104,48 +104,65 @@ services:
FREE_FUNCTION_TIMEOUT: "30"
FREE_FUNCTION_MEMORY: "128"
FREE_FUNCTION_DISK: "512"
PRO_FUNCTION_TIMEOUT: "300"
PRO_FUNCTION_MEMORY: "1024"
PRO_FUNCTION_DISK: "2048"
FREE_FUNCTION_RATELIMIT: "10"
FREE_FUNCTION_ALL_RATELIMIT: "60"
FREE_FUNC_INVOCATIONS_PER_MONTH: "100000"
FREE_BUILD_TIMEOUT_MINUTES: "30"
# Monthly build-minutes cap (0 = unlimited). Local-mode keeps builds
# unconstrained so dev iteration is never blocked.
FREE_BUILD_MAX_MINUTES: "0"
PRO_BUILD_MAX_MINUTES: "0"
# Runtime log retention (TTL) and max search lookback in days.
FREE_LOG_RETENTION_DAYS: "1"
PRO_LOG_RETENTION_DAYS: "30"
PRO_FUNCTION_TIMEOUT: "180"
PRO_FUNCTION_MEMORY: "256"
PRO_FUNCTION_DISK: "1024"
PRO_FUNCTION_RATELIMIT: "0"
PRO_FUNCTION_ALL_RATELIMIT: "0"
PRO_FUNC_INVOCATIONS_PER_MONTH: "0"
PRO_BUILD_TIMEOUT_MINUTES: "60"
FREE_IMAGE_OPTIMIZER_TIMEOUT: "90"
PRO_IMAGE_OPTIMIZER_TIMEOUT: "90"
FREE_IMAGE_OPTIMIZER_MEMORY: "1024"
PRO_IMAGE_OPTIMIZER_MEMORY: "1024"
FREE_IMAGE_OPTIMIZER_DISK: "1024"
PRO_IMAGE_OPTIMIZER_DISK: "1024"
# Custom-domain plan limits (cloud separates FREE/PRO; meaningless locally)
FREE_FRONTEND_CUSTOM_DOMAINS: "10"
PRO_FRONTEND_CUSTOM_DOMAINS: "10"
# Function scheduler limits (cloud: FREE=0, PRO=5; local-mode keeps both
# permissive so signup users on the default FREE plan can still test)
FREE_SCHEDULER_COUNT: "10"
PRO_SCHEDULER_COUNT: "10"
# Function scheduler limits. Sentinel convention: -1 = disabled, 0 =
# unlimited, N = cap. Cloud sets FREE=-1 (disabled), PRO=5; local-mode
# keeps cloud parity for plan-limit testing.
FREE_SCHEDULER_COUNT: "-1"
PRO_SCHEDULER_COUNT: "5"
# Database count caps. Sentinel convention: 0 = unlimited, N = cap. Cloud
# sets FREE=1, PRO=0 (unlimited); local-mode keeps both unlimited so signup
# users on the default FREE plan can create as many databases as they need.
FREE_DATABASE_CAP: "0"
PRO_DATABASE_CAP: "0"
# Database storage caps in MB. 0 = unlimited. Cloud sets FREE=1024 (1 GB),
# PRO=0; local-mode keeps both unlimited so dev databases can grow freely.
FREE_DATABASE_STORAGE_LIMIT_MB: "0"
PRO_DATABASE_STORAGE_LIMIT_MB: "0"
FREE_STORAGE_BUCKETS_PER_PROJECT: "20"
PRO_STORAGE_BUCKETS_PER_PROJECT: "0"
# Deployable regions used by project region resolution (`all_regions=true`
# projects default to this list). Cloud passes the real multi-region set;
# locally we surface a representative subset so feature pickers and
# scheduler validation have something to work with.
AWS_REGIONS: "us-east-1,eu-west-1,ap-southeast-1"
# Stripe Billing (optional)
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY:-}
STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET:-}
STRIPE_PRICE_ID_PRO_MONTHLY: ${STRIPE_PRICE_ID_PRO_MONTHLY:-}
STRIPE_PRICE_ID_PRO_YEARLY: ${STRIPE_PRICE_ID_PRO_YEARLY:-}
STRIPE_PRICE_ID_ADDON_BUILDER_CREDITS_MONTHLY: ${STRIPE_PRICE_ID_ADDON_BUILDER_CREDITS_MONTHLY:-}
STRIPE_PRICE_ID_ADDON_BUILDER_CREDITS_YEARLY: ${STRIPE_PRICE_ID_ADDON_BUILDER_CREDITS_YEARLY:-}
STRIPE_PRICE_ID_ADDON_FUNC_INVOCATIONS_MONTHLY: ${STRIPE_PRICE_ID_ADDON_FUNC_INVOCATIONS_MONTHLY:-}
STRIPE_PRICE_ID_ADDON_FUNC_INVOCATIONS_YEARLY: ${STRIPE_PRICE_ID_ADDON_FUNC_INVOCATIONS_YEARLY:-}
STRIPE_PRICE_ID_ADDON_STORAGE_REQUESTS_MONTHLY: ${STRIPE_PRICE_ID_ADDON_STORAGE_REQUESTS_MONTHLY:-}
STRIPE_PRICE_ID_ADDON_STORAGE_REQUESTS_YEARLY: ${STRIPE_PRICE_ID_ADDON_STORAGE_REQUESTS_YEARLY:-}
STRIPE_PRICE_ID_ADDON_AUTH_REQUESTS_MONTHLY: ${STRIPE_PRICE_ID_ADDON_AUTH_REQUESTS_MONTHLY:-}
STRIPE_PRICE_ID_ADDON_AUTH_REQUESTS_YEARLY: ${STRIPE_PRICE_ID_ADDON_AUTH_REQUESTS_YEARLY:-}
STRIPE_PRICE_ID_ADDON_DATABASE_REQUESTS_MONTHLY: ${STRIPE_PRICE_ID_ADDON_DATABASE_REQUESTS_MONTHLY:-}
STRIPE_PRICE_ID_ADDON_DATABASE_REQUESTS_YEARLY: ${STRIPE_PRICE_ID_ADDON_DATABASE_REQUESTS_YEARLY:-}
STRIPE_PRICE_ID_ADDON_REALTIME_MESSAGES_MONTHLY: ${STRIPE_PRICE_ID_ADDON_REALTIME_MESSAGES_MONTHLY:-}
STRIPE_PRICE_ID_ADDON_REALTIME_MESSAGES_YEARLY: ${STRIPE_PRICE_ID_ADDON_REALTIME_MESSAGES_YEARLY:-}
STRIPE_PRICE_ID_OVERAGE_FUNC_INVOCATIONS_MONTHLY: ${STRIPE_PRICE_ID_OVERAGE_FUNC_INVOCATIONS_MONTHLY:-}
STRIPE_PRICE_ID_OVERAGE_FUNC_INVOCATIONS_YEARLY: ${STRIPE_PRICE_ID_OVERAGE_FUNC_INVOCATIONS_YEARLY:-}
STRIPE_PRICE_ID_OVERAGE_STORAGE_REQUESTS_MONTHLY: ${STRIPE_PRICE_ID_OVERAGE_STORAGE_REQUESTS_MONTHLY:-}
STRIPE_PRICE_ID_OVERAGE_STORAGE_REQUESTS_YEARLY: ${STRIPE_PRICE_ID_OVERAGE_STORAGE_REQUESTS_YEARLY:-}
STRIPE_PRICE_ID_OVERAGE_AUTH_REQUESTS_MONTHLY: ${STRIPE_PRICE_ID_OVERAGE_AUTH_REQUESTS_MONTHLY:-}
STRIPE_PRICE_ID_OVERAGE_AUTH_REQUESTS_YEARLY: ${STRIPE_PRICE_ID_OVERAGE_AUTH_REQUESTS_YEARLY:-}
STRIPE_PRICE_ID_OVERAGE_DATABASE_REQUESTS_MONTHLY: ${STRIPE_PRICE_ID_OVERAGE_DATABASE_REQUESTS_MONTHLY:-}
STRIPE_PRICE_ID_OVERAGE_DATABASE_REQUESTS_YEARLY: ${STRIPE_PRICE_ID_OVERAGE_DATABASE_REQUESTS_YEARLY:-}
STRIPE_PRICE_ID_OVERAGE_REALTIME_MESSAGES_MONTHLY: ${STRIPE_PRICE_ID_OVERAGE_REALTIME_MESSAGES_MONTHLY:-}
STRIPE_PRICE_ID_OVERAGE_REALTIME_MESSAGES_YEARLY: ${STRIPE_PRICE_ID_OVERAGE_REALTIME_MESSAGES_YEARLY:-}
# First-party bootstrap (optional). ANON_KEY_SECRET is sourced from
# .env.local and must match the secret VOLCANO_FIRST_PARTY_ANON_KEY was
# signed with, since the server validates that key during bootstrap.
ANON_KEY_SECRET: ${ANON_KEY_SECRET:-}
VOLCANO_FIRST_PARTY_USER_ID: ${VOLCANO_FIRST_PARTY_USER_ID:-}
VOLCANO_FIRST_PARTY_USER_DISPLAY_NAME: ${VOLCANO_FIRST_PARTY_USER_DISPLAY_NAME:-}
VOLCANO_FIRST_PARTY_USER_TOKEN: ${VOLCANO_FIRST_PARTY_USER_TOKEN:-}
VOLCANO_FIRST_PARTY_PROJECT_ID: ${VOLCANO_FIRST_PARTY_PROJECT_ID:-}
VOLCANO_FIRST_PARTY_PROJECT_NAME: ${VOLCANO_FIRST_PARTY_PROJECT_NAME:-}
VOLCANO_FIRST_PARTY_ANON_KEY: ${VOLCANO_FIRST_PARTY_ANON_KEY:-}
VOLCANO_FIRST_PARTY_DEVICE_CLIENT_ID: ${VOLCANO_FIRST_PARTY_DEVICE_CLIENT_ID:-}
volumes:
- volcano-storage:/app/local-storage
ports:
Expand Down
14 changes: 13 additions & 1 deletion internal/localmode/compose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,22 @@ func TestComposeEnvironmentDefaultsVolcanoImage(t *testing.T) {
func TestDockerComposeTemplateLeavesServerOwnedLocalSecretsUnset(t *testing.T) {
template := string(dockerComposeTemplate)

// The local server generates and owns these secrets, so they must never
// appear in the template at all.
assert.NotContains(t, template, "JWT_SECRET:")
assert.NotContains(t, template, "ENCRYPTION_KEY:")
assert.NotContains(t, template, "ANON_KEY_SECRET:")
assert.NotContains(t, template, "SERVICE_KEY_SECRET:")

// ANON_KEY_SECRET is an optional first-party-bootstrap passthrough. It may
// appear only as an empty ${ANON_KEY_SECRET:-} default (which leaves it unset
// so the server still owns it), never as a hardcoded secret value.
for line := range strings.SplitSeq(template, "\n") {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "ANON_KEY_SECRET:") {
assert.Equal(t, "ANON_KEY_SECRET: ${ANON_KEY_SECRET:-}", trimmed,
"ANON_KEY_SECRET must only be an empty passthrough, not a hardcoded secret")
}
}
}

func TestDockerComposeTemplateExposesLocalFrontendProxy(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions internal/localmode/info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestFetchInfoRunsDockerExecLocalInfo(t *testing.T) {
"default_database_name":"app",
"default_database_region":"local",
"default_database_postgres_version":"16",
"database_url":"postgres://volcano:volcano@localhost:8002/app?sslmode=disable&application_name=volcano_full_access:app",
"database_url":"postgres://volcano_client_22222222-2222-2222-2222-222222222222:vpg_local_secret@localhost:8002/app?sslmode=disable&application_name=volcano_full_access",
Comment thread
subnetmarco marked this conversation as resolved.
"redis_url":"redis://localhost:6379",
"jwt_secret":"server-owned-jwt-secret",
"encryption_key":"server-owned-encryption-key",
Expand All @@ -54,7 +54,7 @@ func TestFetchInfoRunsDockerExecLocalInfo(t *testing.T) {
assert.Equal(t, "app", info.DefaultDatabaseName)
assert.Equal(t, "local", info.DefaultDatabaseRegion)
assert.Equal(t, "16", info.DefaultDatabasePostgresVersion)
assert.Equal(t, "postgres://volcano:volcano@localhost:8002/app?sslmode=disable&application_name=volcano_full_access:app", info.DatabaseURL)
assert.Equal(t, "postgres://volcano_client_22222222-2222-2222-2222-222222222222:vpg_local_secret@localhost:8002/app?sslmode=disable&application_name=volcano_full_access", info.DatabaseURL)
Comment thread
subnetmarco marked this conversation as resolved.
assert.Equal(t, "redis://localhost:6379", info.RedisURL)
assert.Equal(t, "server-owned-jwt-secret", info.JWTSecret)
assert.Equal(t, "server-owned-encryption-key", info.EncryptionKey)
Expand Down
Loading