Hasura gives you an instant, production-grade GraphQL API over a Postgres database (and other sources) without writing resolvers. You point it at a database, track tables, define permissions, and it serves queries, mutations, and subscriptions. Self-hosting the open-source engine is straightforward if you get a handful of environment variables right.
Architecture in one paragraph
Hasura is a single stateless Go binary distributed as the hasura/graphql-engine Docker image. It needs two things: a metadata database (where it stores its own config — table tracking, permissions, relationships) and one or more data sources (the databases it actually exposes). For a simple setup, both can be the same Postgres instance; for production, keep metadata in its own database so engine upgrades and source changes stay isolated.
The environment variables that matter
# Where Hasura stores its metadata
HASURA_GRAPHQL_METADATA_DATABASE_URL=postgres://...
# The database you want to expose (the default source)
PG_DATABASE_URL=postgres://...
# Locks the admin console + admin API
HASURA_GRAPHQL_ADMIN_SECRET=use-a-long-random-string
# Disable the public console in production
HASURA_GRAPHQL_ENABLE_CONSOLE=false
# Don't leak internals in errors
HASURA_GRAPHQL_DEV_MODE=falseThe single most important line is HASURA_GRAPHQL_ADMIN_SECRET. Without it, anyone who finds your URL gets full admin access to your data. Set it before the first deploy, not after.
Deploying the container
Hasura is a pre-built image, so there's no build step — you deploy the image directly with environment variables. Create a container service from the hasura/graphql-engine:v2.x image (pin a real version tag; never latest in production). The engine listens on port 8080.
If you provision your Postgres on PandaStack and attach it, you get a DATABASE_URL injected automatically. Map it into both Hasura variables:
HASURA_GRAPHQL_METADATA_DATABASE_URL=$DATABASE_URL
PG_DATABASE_URL=$DATABASE_URLFor a cleaner production split, provision two databases and point metadata at one and your application data at the other.
| Variable | Purpose | Production value |
|---|---|---|
HASURA_GRAPHQL_ADMIN_SECRET | Admin auth | long random secret |
HASURA_GRAPHQL_ENABLE_CONSOLE | Web console | false |
HASURA_GRAPHQL_DEV_MODE | Verbose errors | false |
HASURA_GRAPHQL_CORS_DOMAIN | CORS allowlist | your frontend origin |
HASURA_GRAPHQL_JWT_SECRET | JWT auth config | your IdP's JWK |
Managing metadata and migrations
Never click around the production console to make schema changes. Use the Hasura CLI so your metadata and migrations live in version control:
hasura init my-project --endpoint https://your-app-url --admin-secret $SECRET
hasura migrate create init --from-server
hasura metadata exportCommit the generated migrations/ and metadata/ directories. Apply them in CI as a deploy step:
hasura migrate apply --endpoint $URL --admin-secret $SECRET
hasura metadata apply --endpoint $URL --admin-secret $SECRETThis is the difference between a reproducible Hasura deployment and a fragile one.
Authentication
Production Hasura almost always uses JWT auth via HASURA_GRAPHQL_JWT_SECRET. Your auth provider issues a token containing Hasura claims (role, user id), and Hasura's row/column permissions enforce access. The admin secret is for tooling and migrations only — your application clients should never see it.
Subscriptions and scaling
Hasura subscriptions are implemented as efficient polling over Postgres, multiplexed so thousands of subscribers don't mean thousands of queries. Still, subscription-heavy workloads put real load on the database. Watch your Postgres connection count: each engine replica holds a pool, so scaling replicas multiplies connections. Tune HASURA_GRAPHQL_PG_CONNECTIONS and consider a connection pooler if you scale horizontally.
Go-live checklist
- Admin secret set and stored as a secret, not in the repo
- Console disabled, dev mode off
- Migrations and metadata applied from version control
- JWT auth configured; permissions defined per role
- Custom domain with automatic SSL
- Connection limits tuned for your replica count
References
- [Hasura deployment docs](https://hasura.io/docs/latest/deployment/deployment-guides/index/)
- [Hasura server environment variables](https://hasura.io/docs/latest/deployment/graphql-engine-flags/reference/)
- [Hasura CLI migrations](https://hasura.io/docs/latest/migrations-metadata-export/index/)
- [Hasura authentication (JWT)](https://hasura.io/docs/latest/auth/authentication/jwt/)
PandaStack runs the Hasura image as a container service with a managed Postgres attached — provision the database, deploy the image, set your admin secret, and you have an instant GraphQL API. Try it free at [dashboard.pandastack.io](https://dashboard.pandastack.io).