Deploy Mastra workers
Run Mastra workers as separate processes so you can scale orchestration, scheduling, and background tasks independently from the API. This guide walks through a fully split deployment using Docker Compose.
This guide covers splitting workers into their own containers. If you only need workers to run in-process alongside the API, see Workers. No extra setup is required.
Before you beginDirect link to Before you begin
You'll need:
- A Mastra application
- Docker and Docker Compose
- A distributed PubSub backend: Redis for
RedisStreamsPubSub, or a Google Cloud project forGoogleCloudPubSub - A shared database reachable from every container. See supported storage backends for the full list.
The default in-memory PubSub can't deliver events across processes. You must configure a distributed PubSub backend before splitting workers into separate containers.
Configure shared infrastructureDirect link to Configure shared infrastructure
Point the Mastra instance at a distributed PubSub backend and a shared database. Use environment variables so the same image runs in every container.
- Redis Streams + PostgreSQL
- Google Cloud Pub/Sub + LibSQL
import { Mastra } from '@mastra/core/mastra'
import { RedisStreamsPubSub } from '@mastra/redis-streams'
import { PostgresStore } from '@mastra/pg'
export const mastra = new Mastra({
storage: new PostgresStore({
connectionString: process.env.DATABASE_URL!,
}),
pubsub: new RedisStreamsPubSub({
url: process.env.REDIS_URL!,
}),
})
import { Mastra } from '@mastra/core/mastra'
import { GoogleCloudPubSub } from '@mastra/google-cloud-pubsub'
import { LibSQLStore } from '@mastra/libsql'
export const mastra = new Mastra({
storage: new LibSQLStore({
url: process.env.DATABASE_URL!,
}),
pubsub: new GoogleCloudPubSub({
projectId: process.env.GCP_PROJECT_ID!,
}),
})
Any supported storage backend works. Swap the storage adapter for your preferred database.
DeployDirect link to Deploy
Build your Mastra application. The output runs in every container.
mastra buildThis produces a self-contained
.mastra/output/directory. See Deploy a Mastra server for details on the build output.Create a Dockerfile that copies the pre-built output and installs production dependencies:
app/DockerfileFROM node:22-alpineWORKDIR /appCOPY .mastra/output/package.json .mastra/output/.npmrc* ./RUN npm install --omit=devCOPY .mastra/output/ .EXPOSE 4111CMD ["node", "index.mjs"]Create a
docker-compose.ymlthat runs the fully split topology. The file defines six services: a database, a PubSub backend, the API server, and three workers. Each worker container runs the same image with a differentMASTRA_WORKERSvalue to control which worker starts.The API container sets
MASTRA_WORKERS: "false"to disable all event processing. The orchestration worker setsMASTRA_STEP_EXECUTION_URLto point step execution requests at the API's internal URL. See step execution URL for details.docker-compose.ymlservices:postgres:image: postgres:16-alpineenvironment:POSTGRES_USER: mastraPOSTGRES_PASSWORD: ${POSTGRES_PASSWORD}POSTGRES_DB: mastraports:- '5432:5432'volumes:- pgdata:/var/lib/postgresql/datahealthcheck:test: ['CMD-SHELL', 'pg_isready -U mastra']interval: 5stimeout: 3sretries: 5redis:image: redis:7-alpineports:- '6379:6379'healthcheck:test: ['CMD', 'redis-cli', 'ping']interval: 5stimeout: 3sretries: 5api:build: ./appports:- '4111:4111'environment:DATABASE_URL: postgres://mastra:${POSTGRES_PASSWORD}@postgres:5432/mastraREDIS_URL: redis://redis:6379MASTRA_WORKERS: 'false'depends_on:postgres:condition: service_healthyredis:condition: service_healthyhealthcheck:test: ['CMD', 'wget', '-qO-', 'http://localhost:4111/api/agents']interval: 5stimeout: 3sretries: 5orchestration-worker:build: ./appenvironment:DATABASE_URL: postgres://mastra:${POSTGRES_PASSWORD}@postgres:5432/mastraREDIS_URL: redis://redis:6379MASTRA_WORKERS: orchestrationMASTRA_STEP_EXECUTION_URL: http://api:4111/apidepends_on:api:condition: service_healthyscheduler-worker:build: ./appenvironment:DATABASE_URL: postgres://mastra:${POSTGRES_PASSWORD}@postgres:5432/mastraREDIS_URL: redis://redis:6379MASTRA_WORKERS: schedulerdepends_on:api:condition: service_healthybackground-task-worker:build: ./appenvironment:DATABASE_URL: postgres://mastra:${POSTGRES_PASSWORD}@postgres:5432/mastraREDIS_URL: redis://redis:6379MASTRA_WORKERS: backgroundTasksdepends_on:api:condition: service_healthyvolumes:pgdata:Create a
.envfile next to yourdocker-compose.yml:.envPOSTGRES_PASSWORD=your-secure-passwordnoteRemember to set any other environment variables your application needs (e.g., your model provider API key).
Start the stack and verify the API responds:
docker compose up -ddocker compose pscurl http://localhost:4111/api/agents
Step execution URLDirect link to Step execution URL
In a fully split deployment, the orchestration worker runs in a separate container from the API. When it processes a workflow event, it delegates step execution to the API over HTTP.
Set MASTRA_STEP_EXECUTION_URL to the API's internal URL, including the /api prefix:
MASTRA_STEP_EXECUTION_URL=http://api:4111/api
The orchestration worker sends a POST request to ${MASTRA_STEP_EXECUTION_URL}/workflows/:workflowId/runs/:runId/steps/execute for each step. The API resolves the workflow and executes the step locally.
Without this variable, the orchestration worker attempts to execute steps in-process. That works when the worker runs alongside the API, but fails in split deployments where the worker doesn't have access to the full Mastra runtime.
ScalingDirect link to Scaling
The orchestration and background task workers are safe to scale horizontally. PubSub consumer groups distribute events across instances, so each event is processed once:
docker compose up -d --scale orchestration-worker=3
docker compose up -d --scale background-task-worker=2
The API can also scale horizontally behind a load balancer.
Don't scale the scheduler worker. Run exactly one instance. Multiple schedulers polling the same storage fire duplicate events for the same schedule.
Crash recoveryDirect link to Crash recovery
Workers recover from crashes because the distributed PubSub backend persists unacknowledged events:
- Orchestration worker: Pending events stay in the PubSub backend. When the worker restarts, it picks up where it left off.
- Scheduler worker: No events are missed permanently. The scheduler computes the next fire time from the current time on restart, not from where it left off.
- API during step execution: The orchestration worker's HTTP request fails. The event is nacked and redelivered on the next attempt.
If the API crashes while a step is already executing (e.g., mid-sleep), that step's work is lost. The workflow run may remain stuck in a running state. Mastra doesn't yet have automatic timeout-based recovery for this scenario.
RelatedDirect link to Related
- Workers: What workers are and when to use them
- Worker authentication: Secure worker-to-API communication
- Workers reference: Configuration details for all worker types
- CLI reference:
mastra worker buildandmastra worker start - PubSub: Event delivery backends
- Deploy a Mastra server: Build output and server configuration