Skip to content

Deployment

Mako ships with a production-ready Dockerfile.

Terminal window
# Build the image from source (default)
docker build -t mako .
# Or: build packages on the host, then package only (what CI does)
pnpm run build:ci
docker build --build-arg USE_PREBUILT=1 -t mako .
# Run it
docker run -p 8080:8080 \
-e DATABASE_URL=mongodb://your-mongodb-url/mako \
-e ENCRYPTION_KEY=your-32-byte-hex-key \
-e SESSION_SECRET=your-session-secret \
-e AI_GATEWAY_API_KEY=your-gateway-key \
mako

The image bundles both the API and the pre-built React frontend. The API serves the frontend from /public.

CI (deploy-app.yml) builds once on the runner with pnpm run build:ci, then packages with USE_PREBUILT=1 and BuildKit registry cache so apt/dbt/pnpm layers are reused across deploys.

Terminal window
# Start MongoDB + app
pnpm run docker:up
# Stop
pnpm run docker:down
# Clean (removes volumes)
pnpm run docker:clean

Mako includes a deploy script for Cloud Run:

Terminal window
# Runs lint, build, local verification, then deploys
./deploy.sh

The script:

  1. Installs dependencies
  2. Runs ESLint
  3. Builds both app and API
  4. Starts the API locally to verify it boots
  5. Builds Docker image and pushes to Google Artifact Registry
  6. Deploys to Cloud Run

Set these in Cloud Run’s environment configuration (or via cloud-run-env.yaml):

Variable Required Purpose
DATABASE_URL Yes MongoDB connection string
ENCRYPTION_KEY Yes Credential encryption
SESSION_SECRET Yes Session security
BASE_URL Yes Public URL (e.g., https://app.mako.ai)
CLIENT_URL Yes Same as BASE_URL for Cloud Run
REDIS_URL Optional Redis pub/sub for resumable streams and realtime — required when running more than one API instance; in-process otherwise
AI_GATEWAY_API_KEY Required AI features (Vercel AI Gateway)
OPENAI_API_KEY Optional Text embeddings only
GOOGLE_CLIENT_ID + SECRET Optional Google OAuth
GH_CLIENT_ID + SECRET Optional GitHub OAuth
SLACK_MCP_CLIENT_ID + SECRET Optional Deployment-wide Slack app for one-click MCP connect (mcp.slack.com). Redirect URI must be ${PUBLIC_URL || CLIENT_URL}/api/mcp/oauth/callback.
SENDGRID_API_KEY Optional Email invitations
EMAIL_LOGO_URL Optional Logo for run-notification emails (default: https://app.mako.ai/email/mako-logo.png). Override for self-hosted/staging.
BILLING_ENABLED Optional Set true to enable Stripe billing (default: false)
STRIPE_SECRET_KEY Optional Stripe secret key (required if billing enabled)
STRIPE_WEBHOOK_SECRET Optional Stripe webhook signing secret
STRIPE_PRO_PRICE_ID Optional Stripe Price ID for the Pro monthly subscription
STRIPE_METER_EVENT_NAME Optional Stripe meter event name for usage reporting (default: llm_usage_usd)
LANGFUSE_PUBLIC_KEY Optional Langfuse LLM observability — public key (tracing is a no-op when unset)
LANGFUSE_SECRET_KEY Optional Langfuse LLM observability — secret key
LANGFUSE_BASE_URL Optional Langfuse region base URL (EU https://cloud.langfuse.com, US https://us.cloud.langfuse.com)
LANGFUSE_TRACING_ENVIRONMENT Optional Trace environment label (defaults to NODE_ENV)
API_RATE_LIMIT_MODE Optional Per-caller API budget: shadow (default, logs who would be refused), enforce (429 + Retry-After), or off. The Cloud Run deploy workflow sets enforce
API_RATE_LIMIT_PER_MINUTE Optional Cost units per caller per minute (default 1200)
EDGE_ORIGIN_SECRET Optional When set, requests must carry x-mako-edge: <secret> (set by a Cloudflare Transform Rule) so the *.run.app URL cannot bypass Cloudflare. Logs only until EDGE_ORIGIN_CHECK=enforce
APPS_LIFECYCLE_ENFORCE Optional Enforces the daily app lifecycle job (stale notices, archiving, paused schedules): all or a comma-separated list of workspace ids. Unset = dry run, the job only logs its plan

Three guards keep one noisy caller from taking an instance down:

  • Per-caller rate limit — each session, token or client IP gets a per-minute budget of cost units, shared across instances through REDIS_URL (without Redis it is per-instance; if Redis is unreachable the limit fails open). Heavy routes (branch listing, sandbox and git reads, query execution, agent chat) cost more than a plain read. Inngest, provider webhooks and health checks are exempt. Over budget, enforce mode answers 429 with Retry-After.
  • Concurrency pool — the git and sandbox read routes allow 12 requests at once per instance. A request that waits more than 10 seconds gets 503 with Retry-After, so a slow endpoint cannot starve the UI, webhooks and Inngest.
  • Edge-origin check — optional; see EDGE_ORIGIN_SECRET above.

Mako runs every BigQuery job as one service account, so INFORMATION_SCHEMA.JOBS cannot split cost by user. Each job therefore carries mako_* labels: mako_source (app_binding, console, adhoc, export, scheduled_query, agent, notebook, workflow, cdc, flow, metadata, connection_test or other) and mako_workspace, plus mako_app, mako_binding, mako_trigger, mako_user, mako_console, mako_flow, mako_table, mako_chat and mako_tool where they apply. Group INFORMATION_SCHEMA.JOBS by these labels to see what an app refresh, a CDC MERGE or an agent query costs. Values are lowercased, any other character becomes _, and they are cut at 63 characters (BigQuery’s label rules). The local BigQuery emulator gets no labels.

Mako can export traces of its AI calls to Langfuse for debugging, cost tracking, and quality evaluation. Tracing is optional and off by default — when the Langfuse keys are unset the bootstrap is a no-op, so local and self-hosted deployments run unaffected.

To enable it, set the keys from your Langfuse project (Settings → API Keys):

LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
# Region base URL — EU: https://cloud.langfuse.com US: https://us.cloud.langfuse.com
LANGFUSE_BASE_URL=https://us.cloud.langfuse.com
# Optional: override the environment label (defaults to NODE_ENV)
# LANGFUSE_TRACING_ENVIRONMENT=production

Only Langfuse/GenAI/LLM spans are exported — unrelated HTTP and database spans are filtered out and never count toward billable units. A defensive redaction pass strips connection strings, bearer tokens, provider API keys, and card numbers before any trace leaves the process.

Mako stores materialized parquet artifacts on the backend: app data bindings, dbt outputs, and chat attachments all share one artifact store. Mako supports three artifact storage backends:

  • filesystem - default; stores parquet files on local disk
  • gcs - stores parquet files in Google Cloud Storage
  • s3 - stores parquet files in an S3-compatible bucket

Select the backend with:

DASHBOARD_ARTIFACT_STORE=filesystem

The DASHBOARD_ prefix on these variable names (and the default /data/dashboard-artifacts directory) is historical; they configure the shared artifact store and are kept for compatibility with existing deployments.

Optional shared settings:

# Prefix for artifact object keys / directories
DASHBOARD_ARTIFACT_PREFIX=dashboards
# Filesystem backend only
DASHBOARD_ARTIFACT_DIR=/data/dashboard-artifacts

To use GCS for parquet artifacts:

DASHBOARD_ARTIFACT_STORE=gcs
GCS_DASHBOARD_BUCKET=your-gcs-bucket
DASHBOARD_ARTIFACT_PREFIX=dashboard-artifacts/prod

The runtime service also needs permission to upload objects, check for existing artifacts, delete stale artifacts, and generate signed read URLs.

  1. Identify the Cloud Run runtime service account:

    Terminal window
    gcloud run services describe revops-fullstack \
    --project=revops-462013 \
    --region=europe-west1 \
    --format="value(spec.template.spec.serviceAccountName)"
  2. Create a bucket in the same project and region:

    Terminal window
    gcloud storage buckets create gs://revops-462013-dashboard-artifacts \
    --project=revops-462013 \
    --location=europe-west1 \
    --uniform-bucket-level-access
  3. Grant object read/write access to the runtime service account:

    Terminal window
    gcloud storage buckets add-iam-policy-binding gs://revops-462013-dashboard-artifacts \
    --project=revops-462013 \
    --member="serviceAccount:813928377715-compute@developer.gserviceaccount.com" \
    --role="roles/storage.objectAdmin"
  4. Grant signed URL capability to the same runtime service account:

    Terminal window
    gcloud iam service-accounts add-iam-policy-binding \
    813928377715-compute@developer.gserviceaccount.com \
    --project=revops-462013 \
    --member="serviceAccount:813928377715-compute@developer.gserviceaccount.com" \
    --role="roles/iam.serviceAccountTokenCreator"
  5. Update the Cloud Run service with the storage env vars:

    Terminal window
    gcloud run services update revops-fullstack \
    --project=revops-462013 \
    --region=europe-west1 \
    --update-env-vars=DASHBOARD_ARTIFACT_STORE=gcs,GCS_DASHBOARD_BUCKET=revops-462013-dashboard-artifacts,DASHBOARD_ARTIFACT_PREFIX=dashboard-artifacts/prod
  • If DASHBOARD_ARTIFACT_STORE is not set, Mako falls back to filesystem.
  • Your deploy workflow must also pass the artifact storage env vars to Cloud Run, or later deploys can revert the backend to local disk.
  • Preview environments should use a unique prefix such as dashboard-artifacts/pr-123 so all previews can safely share the same bucket.

Mako includes an optional Stripe-based subscription billing system. It is disabled by default — self-hosted and open-source deployments have unlimited access to all models and features.

To enable billing for a hosted/SaaS deployment, set BILLING_ENABLED=true and configure the Stripe env vars above.

Plan Free Quota Hard Limit Model Access Max Databases Max Members
Free $5 / month $5 Free-tier models only (≤$3/M) 3 3
Pro $80 / month None (overage billed) All models 50 25

All endpoints are mounted at /api/workspaces/:workspaceId/billing:

Method Endpoint Description
GET /api/workspaces/:wid/billing/status Get current plan, usage, and subscription status
POST /api/workspaces/:wid/billing/checkout Create a Stripe Checkout session (upgrade to Pro)
POST /api/workspaces/:wid/billing/portal Create a Stripe Customer Portal session (manage subscription)

Stripe webhooks are handled at /api/stripe-webhook with signature verification.

Stripe promotion codes/coupons can be applied at checkout. The Stripe Checkout UI includes a promo code input field.

The cloudflare/ directory contains Cloudflare Workers configuration for edge routing and proxy functionality.