Hasura provides an instant GraphQL API over your PostgreSQL database without writing backend code. It generates queries, mutations, subscriptions, and handles authorization — all configured through a web console. This guide covers deploying Hasura on a VPS for production use.
Installation with Docker
# docker-compose.yml
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: postgrespassword
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 5s
timeout: 5s
retries: 5
hasura:
image: hasura/graphql-engine:v2.40.0
ports:
- "8080:8080"
depends_on:
postgres:
condition: service_healthy
environment:
HASURA_GRAPHQL_METADATA_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/postgres
HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/postgres
HASURA_GRAPHQL_ENABLE_CONSOLE: "true"
HASURA_GRAPHQL_ADMIN_SECRET: your-admin-secret-key
HASURA_GRAPHQL_JWT_SECRET: |
{"type":"HS256","key":"your-jwt-secret-minimum-32-chars-long"}
HASURA_GRAPHQL_UNAUTHORIZED_ROLE: anonymous
HASURA_GRAPHQL_CORS_DOMAIN: "https://example.com,https://app.example.com"
restart: unless-stopped
volumes:
pgdata:
# Start Hasura
docker compose up -d
# Access console at http://your-vps:8080/console
# Use admin secret to authenticate
Tracking Tables
-- Create tables in PostgreSQL
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
role VARCHAR(20) DEFAULT 'user',
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT,
author_id INTEGER REFERENCES users(id),
published BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT NOW()
);
-- In Hasura Console: Data tab > Track All tables
-- Hasura auto-detects relationships from foreign keys
Queries and Mutations
# Auto-generated GraphQL queries:
query GetUsers {
users(order_by: { created_at: desc }, limit: 20) {
id
name
email
posts {
id
title
published
}
}
}
mutation CreateUser {
insert_users_one(object: { name: "Alice", email: "alice@example.com" }) {
id
name
}
}
# Real-time subscription
subscription NewPosts {
posts(where: { published: { _eq: true } }, order_by: { created_at: desc }) {
id
title
author { name }
}
}
Permission Rules
# Hasura Console > Permissions tab
# Row-level security based on JWT claims
# User role - can only see own data:
# Select permission on users table:
# Row filter: {"id": {"_eq": "X-Hasura-User-Id"}}
# Column access: id, name, email (not role)
# User role - can only see published posts:
# Select permission on posts table:
# Row filter: {"_or": [{"published": {"_eq": true}}, {"author_id": {"_eq": "X-Hasura-User-Id"}}]}
Remote Schemas and Actions
# Add custom business logic via Actions
# Hasura Console > Actions > Create
# Action definition
type Mutation {
sendEmail(to: String!, subject: String!, body: String!): EmailResponse
}
type EmailResponse {
success: Boolean!
messageId: String
}
# Handler URL: https://your-api.example.com/api/send-email
# Hasura forwards the mutation to your custom endpoint
Nginx Reverse Proxy
server {
listen 443 ssl http2;
server_name graphql.example.com;
ssl_certificate /etc/letsencrypt/live/graphql.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/graphql.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Metadata Management
# Export metadata for version control
hasura metadata export
# Apply metadata
hasura metadata apply
# Structure:
# metadata/
# databases/
# actions.yaml
# remote_schemas.yaml
# tables.yaml
Summary
Hasura eliminates the need to write CRUD API code by generating a complete GraphQL API directly from your PostgreSQL schema. Real-time subscriptions, row-level permissions, and remote schema stitching cover most API needs. For custom business logic, Actions forward specific mutations to your own endpoints. Self-hosting on a VPS gives you unlimited API requests, full data control, and the ability to run Hasura alongside your existing database.