This documentation is for versions 0.1 – 0.6
. You may want to view the
latest version.
Deploy
You can deploy your Ponder app to any cloud environment that supports Node.js.
Railway
Railway's general-purpose cloud platform is a great starting point for most Ponder apps.
Log in to Railway
Connect your GitHub account, and make sure that your Ponder app has been pushed to remote.
Create a Ponder app service
From the Railway console:
- Click New Project → Deploy from GitHub repo and select your repo from the list
- Click Add variables, then add RPC URLs (e.g.
PONDER_RPC_URL_1
) and other environment variables - Create a public domain. In Settings → Networking, click Generate Domain
- Set the healthcheck path and timeout. In Settings → Deploy, set the Healthcheck Path to
/ready
and the Healthcheck Timeout to86400
seconds (1 day)
Monorepo users: Configure the Root Directory and Start Command such
that ponder start
runs at the Ponder project root. For example, set the root
directory to packages/ponder
or set the start command to cd packages/ponder && pnpm start
.
Create a Postgres database
From the new project dashboard:
- Click Create → Database → Add PostgreSQL
- Open the Variables tab for the Ponder app service, click New Variable → Add Reference → select
DATABASE_URL
and click Add
After a moment, the Ponder app service should redeploy successfully. Check the Build Logs and Deploy Logs tabs to debug any issues.
Self hosting
In general, hosting a Ponder app is similar to hosting a normal Node.js HTTP server. Rather than offer a step-by-step guide, this section describes the key Ponder-specific quirks to consider when self-hosting.
Database connection
You app will have performance issues if the roundtrip database latency exceeds ~20 milliseconds. This is common when using a database in different private network or region.
In production, Ponder works best with a Postgres database in the same private network. Set the DATABASE_URL
environment variable to the connection string of your Postgres database, or manually override the database.connectionString
option in ponder.config.ts
.
import { createConfig } from "@ponder/core";
export default createConfig({
database: {
kind: "postgres",
connectionString: "postgres://user:password@mycloud.internal:5432/database",
},
// ... more config
});
Database schema
When a Ponder app starts up, it attempts to create user tables (defined in ponder.schema.ts
) in the target schema. If another Ponder app is currently using that schema, the app will crash shortly after startup with the error Schema 'public' is locked by a different Ponder app
. This locking mechanism avoids issues caused by two Ponder apps writing to the same tables simultaneously.
The default schema is public
. This works fine during development, but causes problems in production when multiple deployments share the same database. During a zero-downtime redeployment, the new deployment will fail because the public
schema is locked by the old deployment.
To avoid this problem, set the schema
option in ponder.config.ts
to a deployment-specific value. The best choice for schema
depends on your environment - on Railway, Ponder automatically detects and uses the RAILWAY_DEPLOYMENT_ID
environment variable. In a Kubernetes cluster, the pod name is often a great choice:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ponder-deployment
spec:
selector:
matchLabels:
app: ponder
template:
metadata:
labels:
app: ponder
spec:
containers:
- name: ponder
# ...
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
import { createConfig } from "@ponder/core";
export default createConfig({
database: {
kind: "postgres",
connectionString: process.env.DATABASE_URL,
schema: process.env.POD_NAME,
},
// ... more config
});
Health checks & probes
The 0.6
release added the /ready
endpoint and simplified /health
. Read
the migration guide.
Use the /health
and /ready
endpoints to configure health checks or probes.
/health
: Returns an HTTP200
response immediately after the process starts./ready
: Returns an HTTP200
response once indexing progress has reached realtime across all chains. During the historical backfill, the endpoint returns an HTTP503
response.