Hasura Docker Compose ile Kurulum ve İlk Yapılandırma

Eğer modern bir uygulama geliştiriyorsanız ve veritabanınızın üzerinde hızlıca bir GraphQL API’si ayağa kaldırmak istiyorsanız, Hasura tam olarak aradığınız araç. Üstelik Docker Compose ile kurulum yapmak, geliştirme ortamınızı dakikalar içinde hazır hale getiriyor. Bu yazıda sıfırdan başlayıp production’a taşıyabileceğiniz sağlam bir Hasura kurulumu yapacağız.

Hasura Nedir ve Neden Kullanmalıyız

Hasura, PostgreSQL veritabanınızı otomatik olarak tam özellikli bir GraphQL API’sine dönüştüren açık kaynaklı bir motor. Schema oluşturmak, resolver yazmak, N+1 problemleriyle boğuşmak yerine sadece veritabanı tablolarınızı tanımlıyorsunuz ve Hasura gerisini hallediyor.

Şu özellikleri sizi ikna edebilir:

  • Gerçek zamanlı subscriptions desteği kutudan çıkıyor
  • Role tabanlı yetkilendirme sistemi oldukça esnek
  • Remote schema ve Action desteğiyle custom iş mantığı eklenebiliyor
  • Migration yönetimi entegre geliyor
  • Webhook desteğiyle event-driven mimari kurulabiliyor

Bir e-ticaret projesi düşünün. Ürünler, siparişler, kullanıcılar gibi onlarca tablo var. Geleneksel yaklaşımda her entity için ayrı endpoint yazmak, dokümantasyon tutmak, versiyon yönetimi yapmak saatler alır. Hasura ile bunu dakikalar içinde hallediyorsunuz.

Ön Gereksinimler

Başlamadan önce sistemde şunların kurulu olması gerekiyor:

  • Docker Engine 20.10 ve üzeri
  • Docker Compose v2 (eski docker-compose yerine docker compose komutu)
  • En az 2 GB RAM (geliştirme için yeterli)
  • 10 GB boş disk alanı

Versiyonları kontrol edelim:

docker --version
docker compose version

Eğer Docker kurulu değilse Ubuntu/Debian için hızlıca kuralım:

curl -fsSL https://get.docker.com | bash
sudo usermod -aG docker $USER
newgrp docker

Proje Dizin Yapısı Oluşturma

Düzenli bir dizin yapısıyla başlamak, ilerleyen süreçte hayatınızı kolaylaştırır. Özellikle migration dosyaları ve metadata birikmeye başlayınca bu yapının önemi ortaya çıkıyor.

mkdir -p hasura-project/{migrations,metadata,seeds}
cd hasura-project

Dizin yapımız şöyle görünecek:

  • migrations/: Veritabanı şema değişikliklerini tutar
  • metadata/: Hasura konfigürasyon ve permission tanımlarını tutar
  • seeds/: Test verilerini tutar

Docker Compose Dosyası Hazırlama

Ana yapılandırma dosyamızı oluşturalım. Bu dosya üç servisi bir arada yönetecek: PostgreSQL, Hasura Engine ve pgAdmin (opsiyonel ama pratik).

version: "3.8"

services:
  postgres:
    image: postgres:15-alpine
    container_name: hasura-postgres
    restart: unless-stopped
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init-scripts:/docker-entrypoint-initdb.d
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_DB: ${POSTGRES_DB}
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - hasura-network

  hasura:
    image: hasura/graphql-engine:v2.35.0
    container_name: hasura-engine
    restart: unless-stopped
    ports:
      - "8080:8080"
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      HASURA_GRAPHQL_DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
      HASURA_GRAPHQL_ENABLE_CONSOLE: "true"
      HASURA_GRAPHQL_DEV_MODE: "true"
      HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
      HASURA_GRAPHQL_ADMIN_SECRET: ${HASURA_ADMIN_SECRET}
      HASURA_GRAPHQL_JWT_SECRET: ${HASURA_JWT_SECRET}
      HASURA_GRAPHQL_UNAUTHORIZED_ROLE: public
      HASURA_GRAPHQL_CORS_DOMAIN: "*"
      HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES: "false"
    volumes:
      - ./migrations:/hasura-migrations
      - ./metadata:/hasura-metadata
    networks:
      - hasura-network

  pgadmin:
    image: dpage/pgadmin4:latest
    container_name: hasura-pgadmin
    restart: unless-stopped
    environment:
      PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL}
      PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD}
    ports:
      - "5050:80"
    depends_on:
      - postgres
    networks:
      - hasura-network

volumes:
  postgres_data:
    driver: local

networks:
  hasura-network:
    driver: bridge

Bu compose dosyasında dikkat edilmesi gereken bazı noktalar var. depends_on ile condition: service_healthy kullanımı kritik. Hasura, PostgreSQL tamamen hazır olmadan bağlanmaya çalışırsa hata alırsınız. Healthcheck bu sorunu çözüyor.

Environment Değişkenleri

Hassas bilgileri compose dosyasına yazmak kötü pratik. .env dosyası oluşturalım:

cat > .env << 'EOF'
# PostgreSQL
POSTGRES_USER=hasura_user
POSTGRES_PASSWORD=guclu_bir_sifre_koy_buraya
POSTGRES_DB=hasura_db

# Hasura
HASURA_ADMIN_SECRET=admin_secret_en_az_32_karakter_olmali
HASURA_JWT_SECRET={"type":"HS256","key":"jwt_secret_key_en_az_32_karakter"}

# pgAdmin
[email protected]
PGADMIN_PASSWORD=pgadmin_sifresi
EOF

.gitignore dosyasına .env‘i eklemeyi unutmayın:

echo ".env" >> .gitignore
echo "*.env" >> .gitignore

Production ortamında bu değerleri Docker secrets veya HashiCorp Vault gibi araçlarla yönetmek daha güvenli. Geliştirme için .env yeterli.

Servisleri Ayağa Kaldırma

Her şey hazır, çalıştıralım:

docker compose up -d

# Logları takip etmek için
docker compose logs -f hasura

# Sadece son 50 satır görmek için
docker compose logs --tail=50 hasura

Servisler başarıyla ayağa kalktığında şu URL’leri kullanabilirsiniz:

  • Hasura Console: http://localhost:8080
  • GraphQL Endpoint: http://localhost:8080/v1/graphql
  • pgAdmin: http://localhost:5050

Hasura Console’a girince admin secret soracak, .env dosyasında tanımladığınız değeri girin.

İlk Veritabanı Tabloları ve Migration

Hasura Console üzerinden tablo oluşturabilirsiniz ama migration dosyalarıyla yönetmek çok daha profesyonel. Hasura CLI kuralım:

# Linux/Mac için
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash

# Versiyonu kontrol et
hasura version

Proje klasöründe Hasura CLI’yi initialize edelim:

hasura init . --endpoint http://localhost:8080 --admin-secret admin_secret_en_az_32_karakter_olmali --directory .

Örnek bir e-ticaret senaryosu için migration oluşturalım:

hasura migrate create "initial_schema" --database-name default

Bu komut migrations/default/ altında iki dosya oluşturur: up.sql ve down.sql. up.sql dosyasını düzenleyelim:

-- migrations/default/1234567890_initial_schema/up.sql

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email VARCHAR(255) UNIQUE NOT NULL,
    full_name VARCHAR(255) NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE categories (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    slug VARCHAR(100) UNIQUE NOT NULL,
    description TEXT,
    parent_id INTEGER REFERENCES categories(id)
);

CREATE TABLE products (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(255) NOT NULL,
    description TEXT,
    price NUMERIC(10, 2) NOT NULL,
    stock_quantity INTEGER NOT NULL DEFAULT 0,
    category_id INTEGER REFERENCES categories(id),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE orders (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id),
    status VARCHAR(50) NOT NULL DEFAULT 'pending',
    total_amount NUMERIC(10, 2) NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE order_items (
    id SERIAL PRIMARY KEY,
    order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
    product_id UUID NOT NULL REFERENCES products(id),
    quantity INTEGER NOT NULL,
    unit_price NUMERIC(10, 2) NOT NULL
);

-- updated_at otomatik güncellemek için trigger
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ language 'plpgsql';

CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users
    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER update_products_updated_at BEFORE UPDATE ON products
    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER update_orders_updated_at BEFORE UPDATE ON orders
    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

Migration’ı uygulayalım:

hasura migrate apply --database-name default

Hasura Metadata Yapılandırması

Migration’lar veritabanı şemasını yönetir, metadata ise Hasura’nın davranışını. Permission’lar, relationships, computed fields gibi her şey metadata içinde tutuluyor.

Tabloları Hasura’ya tanıtalım ve metadata’ya ekleyelim:

hasura metadata apply

Eğer Console üzerinden tablo eklediyseniz mevcut metadata’yı çekelim:

hasura metadata export

Permission yapılandırması için örnek bir senaryo. users tablosu için bir kullanıcı sadece kendi verisini görebilmeli:

# metadata/databases/default/tables/public_users.yaml dosyasını düzenle
cat > metadata/databases/default/tables/public_users.yaml << 'EOF'
table:
  name: users
  schema: public
select_permissions:
  - role: user
    permission:
      columns:
        - id
        - email
        - full_name
        - created_at
      filter:
        id:
          _eq: X-Hasura-User-Id
      limit: 1
  - role: admin
    permission:
      columns: "*"
      filter: {}
insert_permissions:
  - role: user
    permission:
      columns:
        - email
        - full_name
      check:
        id:
          _eq: X-Hasura-User-Id
EOF

Çalışan GraphQL Sorguları

Hasura Console’daki GraphiQL arayüzünden test edelim. Önce birkaç test verisi ekleyelim:

mutation InsertTestData {
  insert_categories(objects: [
    {name: "Elektronik", slug: "elektronik"},
    {name: "Giyim", slug: "giyim"}
  ]) {
    affected_rows
    returning {
      id
      name
    }
  }
}

Ürünleri sorgulayalım:

query GetProductsWithCategory {
  products(
    where: {stock_quantity: {_gt: 0}},
    order_by: {created_at: desc},
    limit: 10
  ) {
    id
    name
    price
    stock_quantity
    category {
      name
      slug
    }
  }
}

Subscription ile gerçek zamanlı sipariş takibi:

subscription WatchOrderStatus($orderId: uuid!) {
  orders_by_pk(id: $orderId) {
    id
    status
    total_amount
    updated_at
    order_items {
      quantity
      unit_price
      product {
        name
      }
    }
  }
}

Servis Yönetimi ve Bakım Komutları

Günlük operasyonlarda işinize yarayacak komutlar:

# Servisleri durdur (verileri koru)
docker compose stop

# Servisleri tamamen kaldır (verileri koru)
docker compose down

# Verileri de sil (dikkatli ol!)
docker compose down -v

# Sadece Hasura'yı yeniden başlat
docker compose restart hasura

# Container kaynak kullanımını izle
docker stats hasura-engine hasura-postgres

# PostgreSQL'e doğrudan bağlan
docker exec -it hasura-postgres psql -U hasura_user -d hasura_db

# Hasura container'ına bash aç
docker exec -it hasura-engine /bin/sh

# Log boyutunu kontrol et
docker inspect hasura-engine | grep LogPath

Migration durumunu kontrol etmek için:

# Mevcut migration durumu
hasura migrate status --database-name default

# Belirli bir migration'a geri dön
hasura migrate apply --goto 1234567890 --database-name default

# Son migration'ı geri al
hasura migrate apply --down 1 --database-name default

Production için Ek Güvenlik Önlemleri

Geliştirme ortamında rahat çalışmak için açık bıraktığımız bazı ayarlar production’da kapatılmalı.

Console’u production’da kapatın, doğrudan erişimi engelleyin:

# .env.production dosyası
HASURA_GRAPHQL_ENABLE_CONSOLE=false
HASURA_GRAPHQL_DEV_MODE=false
HASURA_GRAPHQL_ENABLED_LOG_TYPES=startup,http-log,webhook-log

Rate limiting için Nginx reverse proxy ekleyin. nginx.conf örneği:

upstream hasura {
    server hasura:8080;
}

limit_req_zone $binary_remote_addr zone=graphql:10m rate=30r/m;

server {
    listen 80;
    server_name api.yourdomain.com;

    location /v1/graphql {
        limit_req zone=graphql burst=10 nodelay;
        proxy_pass http://hasura;
        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;
    }

    location /healthz {
        proxy_pass http://hasura/healthz;
    }
}

Yaygın Sorunlar ve Çözümleri

Hasura PostgreSQL’e bağlanamıyor: Healthcheck’i kontrol edin. postgres servisinin healthy durumuna gelmesi birkaç saniye alabilir. docker compose ps ile durum kontrol edin.

Migration çakışmaları: Aynı anda iki geliştirici migration oluşturursa timestamp çakışabilir. hasura migrate squash komutu ile migration’ları birleştirebilirsiniz.

Console’da performans sorunu: HASURA_GRAPHQL_QUERY_PLAN_CACHE_SIZE değerini artırın, varsayılan 1000’dir.

JWT doğrulama hataları: JWT secret’ın en az 32 karakter olmasına dikkat edin. JSON formatının doğru olduğunu kontrol edin.

Container restart loop: docker compose logs hasura ile hata mesajını okuyun. Genellikle database URL veya admin secret formatı hatalıdır.

Sonuç

Hasura ile Docker Compose kurulumu, ilk bakışta karmaşık görünse de doğru yapılandırıldığında son derece güçlü bir geliştirme altyapısı ortaya çıkıyor. Migration tabanlı schema yönetimi, metadata ile permission kontrolü ve CLI araçlarıyla birlikte profesyonel bir GraphQL API geliştirme ortamınız hazır.

Bu kurulumu temel alarak şunları yapabilirsiniz:

  • Remote schema ile mevcut REST API’lerinizi entegre edebilirsiniz
  • Hasura Actions ile custom iş mantığı ekleyebilirsiniz
  • Event triggers ile veritabanı değişikliklerini webhook’lara bağlayabilirsiniz
  • Hasura Cloud’a geçerek yönetilen servis kullanabilirsiniz

Geliştirme ortamında docker compose up -d ile dakikalar içinde başlayıp, migration ve metadata yönetimiyle takım arkadaşlarınızla senkronize çalışmak artık çok daha kolay. Production’a taşırken güvenlik ayarlarını sıkılaştırmayı ve mutlaka bir reverse proxy önüne koymayı unutmayın.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir