⌘K

Self-Hosting Nativeblocks with Docker

Self-Hosting Nativeblocks with Docker
Alireza Fard

Alireza Fard

26/05/2026

DockerSelf-hostingDevOps

A complete guide to running Nativeblocks Core API and Studio on your own infrastructure using Docker and Docker Compose.

Nativeblocks Cloud handles everything out of the box: hosting, scaling, updates, and uptime. For most teams, that's the right starting point. But some teams can't use a shared cloud for compliance reasons, want to keep frame data inside their own network, or need to run on airgapped infrastructure. For those cases, Nativeblocks supports full self-hosting through Docker.

This guide walks through deploying both services you need: the Core API (the backend that stores and delivers your frames) and the Studio (the web dashboard and visual editor).


What You're Deploying

Self-hosting Nativeblocks means running two services:

ServiceImageWhat it does
nativeblocks-core-apinativeblocks/core-api:latestStores frames, validates licenses, serves the GraphQL API, handles SDK gateway calls
nativeblocks-studionativeblocks/nativeblocks-studio:selfhostThe web editor that connects to your self-hosted Core API

The Core API depends on three pieces of external infrastructure: PostgreSQL (primary database), Valkey (cache and event queue), and S3-compatible object storage (for frame assets). The Studio is a frontend application and has no database of its own.


Prerequisites

Before you start, make sure you have:

  • Docker 24+ on the host machine
  • A running PostgreSQL 15+ instance
  • A running Valkey or Redis 7+ instance
  • An S3-compatible storage bucket (AWS S3, DigitalOcean Spaces, MinIO, or similar)
  • A Nativeblocks license key from nativeblocks.io

Database migrations run automatically at startup. You don't need to run anything manually.


Deploying the Core API

Environment variables

The Core API is configured entirely through environment variables.

VariableRequiredDescription
PORTNoHTTP port (default: 8080)
POSTGRES_CONNECTION_URLYesPostgreSQL DSN
VALKEY_CONNECTION_URLYesValkey/Redis URL
S3_ACCESS_KEY_IDYesS3 access key
S3_SECRET_ACCESS_KEYYesS3 secret key
S3_ENDPOINTYesS3 endpoint URL
GATEWAYYesSDK delivery format: GRAPHQL
ADMIN_ENDPOINTYesLicense validation endpoint; use https://admin.api.nativeblocks.io
CORE_ENDPOINTYesPublicly reachable URL of your Core API
LICENSE_KEYYesYour Nativeblocks license key
LOG_LEVELNoverbose (default), warning, or error; applies to stdout only
LOG_PROVIDERSNoComma-separated: stdout (default), sentry
SENTRY_DSNNoRequired when sentry is in LOG_PROVIDERS. Sentry only receives error-level logs; LOG_LEVEL does not affect what Sentry captures

CORE_ENDPOINT must be the URL your mobile apps and Studio will use to reach the API, not localhost. If the API is behind a reverse proxy, this should be the public domain.

Docker run

docker pull nativeblocks/core-api:latest

docker run -d \
  --name nativeblocks-core-api \
  --restart unless-stopped \
  -p 8080:8080 \
  -e PORT=8080 \
  -e POSTGRES_CONNECTION_URL="postgresql://user:pass@your-db-host:5432/nativeblocks?sslmode=require" \
  -e VALKEY_CONNECTION_URL="redis://your-valkey-host:6379" \
  -e S3_ACCESS_KEY_ID="<your-access-key>" \
  -e S3_SECRET_ACCESS_KEY="<your-secret-key>" \
  -e S3_ENDPOINT="https://s3.amazonaws.com" \
  -e GATEWAY=GRAPHQL \
  -e ADMIN_ENDPOINT=https://admin.api.nativeblocks.io \
  -e CORE_ENDPOINT=https://api.yourcompany.com \
  -e LICENSE_KEY="<your-license-key>" \
  -e LOG_LEVEL=error \
  -e LOG_PROVIDERS=stdout \
  nativeblocks/core-api:latest

Once the container starts, the API is available at the endpoints below:

PathPurpose
/graphqlMain GraphQL API
/graphql/playgroundInteractive query explorer
/gateway/initSDK gateway initialization
/metricsPrometheus metrics

Deploying the Studio

The Studio is a frontend application that talks to whichever Core API you point it at. It has no database or state of its own.

Environment variables

VariableRequiredDescription
VITE_WEBSITE_URLYesDashboard URL, e.g. https://nativeblocks.io/dashboard
VITE_API_ENDPOINT_GRAPHQL_URLYesCore API GraphQL endpoint
VITE_API_REALTIME_ENDPOINT_URLYesRealtime WebSocket endpoint
VITE_SENTRY_DSNNoSentry DSN for error reporting; only error-level events are sent to Sentry

Docker run

docker pull nativeblocks/nativeblocks-studio:selfhost

docker run -d \
  --name nativeblocks-studio \
  --restart unless-stopped \
  -p 3000:80 \
  -e VITE_WEBSITE_URL="https://nativeblocks.io/dashboard" \
  -e VITE_API_ENDPOINT_GRAPHQL_URL="https://api.yourcompany.com/graphql" \
  -e VITE_API_REALTIME_ENDPOINT_URL="wss://api.yourcompany.com/hotReload" \
  nativeblocks/nativeblocks-studio:selfhost

Full Stack with Docker Compose

The cleanest way to run everything together (including PostgreSQL and Valkey) is a single docker-compose.yml. This is the recommended approach for teams standing up a new self-hosted instance.

version: "3.9"

volumes:
  postgres_data:
  valkey_data:

services:
  postgres:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_DB: nativeblocks
      POSTGRES_USER: nativeblocks
      POSTGRES_PASSWORD: <strong-password>
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U nativeblocks"]
      interval: 10s
      timeout: 5s
      retries: 5

  valkey:
    image: valkey/valkey:8
    restart: unless-stopped
    volumes:
      - valkey_data:/data
    healthcheck:
      test: ["CMD", "valkey-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  core-api:
    image: nativeblocks/core-api:latest
    restart: unless-stopped
    ports:
      - "8080:8080"
    depends_on:
      postgres:
        condition: service_healthy
      valkey:
        condition: service_healthy
    environment:
      PORT: "8080"
      POSTGRES_CONNECTION_URL: "postgresql://nativeblocks:<strong-password>@postgres:5432/nativeblocks?sslmode=disable"
      VALKEY_CONNECTION_URL: "redis://valkey:6379"
      S3_ACCESS_KEY_ID: "<your-access-key>"
      S3_SECRET_ACCESS_KEY: "<your-secret-key>"
      S3_ENDPOINT: "https://s3.amazonaws.com"
      GATEWAY: "GRAPHQL"
      ADMIN_ENDPOINT: "https://admin.api.nativeblocks.io"
      CORE_ENDPOINT: "https://api.yourcompany.com"
      LICENSE_KEY: "<your-license-key>"
      LOG_LEVEL: "error"
      LOG_PROVIDERS: "stdout"

  studio:
    image: nativeblocks/nativeblocks-studio:selfhost
    restart: unless-stopped
    ports:
      - "3000:80"
    environment:
      VITE_WEBSITE_URL: "https://nativeblocks.io/dashboard"
      VITE_API_ENDPOINT_GRAPHQL_URL: "https://api.yourcompany.com/graphql"
      VITE_API_REALTIME_ENDPOINT_URL: "wss://api.yourcompany.com/hotReload"

Start everything:

docker compose up -d

Check that all services came up healthy:

docker compose ps

Putting It Behind a Reverse Proxy

In production you'll want HTTPS and a real domain in front of both services. Here's an Nginx configuration for both.

Core API (api.yourcompany.com)

server {
    listen 443 ssl;
    server_name api.yourcompany.com;

    ssl_certificate     /etc/ssl/certs/yourcompany.crt;
    ssl_certificate_key /etc/ssl/private/yourcompany.key;

    client_max_body_size 20M;

    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;
        proxy_read_timeout 60s;
        proxy_send_timeout 60s;
    }
}

server {
    listen 80;
    server_name api.yourcompany.com;
    return 301 https://$host$request_uri;
}

Studio (studio.yourcompany.com)

server {
    listen 443 ssl;
    server_name studio.yourcompany.com;

    ssl_certificate     /etc/ssl/certs/yourcompany.crt;
    ssl_certificate_key /etc/ssl/private/yourcompany.key;

    location / {
        proxy_pass         http://127.0.0.1:3000;
        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;
    }
}

server {
    listen 80;
    server_name studio.yourcompany.com;
    return 301 https://$host$request_uri;
}

Make sure both domains match what you put in CORE_ENDPOINT and VITE_API_ENDPOINT_GRAPHQL_URL. Using localhost in those variables will break SDK calls and Studio connectivity once you're behind HTTPS.


License Validation

The Core API calls ADMIN_ENDPOINT on startup and periodically while running to validate your license. If validation fails the process exits. Make sure:

  • https://admin.api.nativeblocks.io is reachable from the container (outbound HTTPS)
  • LICENSE_KEY is valid and not expired

If you're in a restricted network environment, you may need to add an egress rule to allow traffic to admin.api.nativeblocks.io on port 443.


Monitoring

Prometheus metrics are exposed at /metrics on the Core API. Example scrape config:

scrape_configs:
  - job_name: nativeblocks-core-api
    static_configs:
      - targets: ["api.yourcompany.com:8080"]
    metrics_path: /metrics

Upgrading

Pull the new image and restart the container. Migrations run automatically on startup.

# Core API
docker pull nativeblocks/core-api:<new-version>
docker compose up -d --no-deps core-api

# Studio
docker pull nativeblocks/nativeblocks-studio:selfhost
docker compose up -d --no-deps studio

Migrations are forward-only. Always back up your PostgreSQL database before upgrading to a new major version of the Core API.


Minimum Resource Recommendations

ComponentCPUMemory
core-api2 vCPU2 GB
studio0.5 vCPU512 MB
postgres1 vCPU1 GB
valkey0.5 vCPU512 MB

These are starting points. Adjust based on your team size and frame deployment frequency.


Next Steps

Once your self-hosted instance is running:

  • Point your Nativeblocks CLI at CORE_ENDPOINT instead of the default cloud endpoint
  • Open the Studio at studio.yourcompany.com and sign in
  • Deploy a frame with frame deploy — it goes to your instance, not Nativeblocks Cloud

If you hit issues, the startup logs on the core-api container are the first place to look. Set LOG_LEVEL=verbose temporarily to see the full migration and license validation output on stdout; this does not affect what gets sent to Sentry, which only ever receives error-level logs.

Continue Reading