Back to Blog
Tutorial11 min read2026-06-29

How to Deploy the Hasura GraphQL Engine

Self-host the Hasura GraphQL Engine on top of PostgreSQL: container config, the environment variables that actually matter, metadata migrations, and locking down the admin secret before you go live.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

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=false

The 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_URL

For a cleaner production split, provision two databases and point metadata at one and your application data at the other.

VariablePurposeProduction value
HASURA_GRAPHQL_ADMIN_SECRETAdmin authlong random secret
HASURA_GRAPHQL_ENABLE_CONSOLEWeb consolefalse
HASURA_GRAPHQL_DEV_MODEVerbose errorsfalse
HASURA_GRAPHQL_CORS_DOMAINCORS allowlistyour frontend origin
HASURA_GRAPHQL_JWT_SECRETJWT auth configyour 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 export

Commit 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 $SECRET

This 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).

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also