Self-Hosting Nativeblocks with Docker

Alireza Fard
26/05/2026
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:
| Service | Image | What it does |
|---|---|---|
nativeblocks-core-api | nativeblocks/core-api:latest | Stores frames, validates licenses, serves the GraphQL API, handles SDK gateway calls |
nativeblocks-studio | nativeblocks/nativeblocks-studio:selfhost | The 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.
| Variable | Required | Description |
|---|---|---|
PORT | No | HTTP port (default: 8080) |
POSTGRES_CONNECTION_URL | Yes | PostgreSQL DSN |
VALKEY_CONNECTION_URL | Yes | Valkey/Redis URL |
S3_ACCESS_KEY_ID | Yes | S3 access key |
S3_SECRET_ACCESS_KEY | Yes | S3 secret key |
S3_ENDPOINT | Yes | S3 endpoint URL |
GATEWAY | Yes | SDK delivery format: GRAPHQL |
ADMIN_ENDPOINT | Yes | License validation endpoint; use https://admin.api.nativeblocks.io |
CORE_ENDPOINT | Yes | Publicly reachable URL of your Core API |
LICENSE_KEY | Yes | Your Nativeblocks license key |
LOG_LEVEL | No | verbose (default), warning, or error; applies to stdout only |
LOG_PROVIDERS | No | Comma-separated: stdout (default), sentry |
SENTRY_DSN | No | Required 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:
| Path | Purpose |
|---|---|
/graphql | Main GraphQL API |
/graphql/playground | Interactive query explorer |
/gateway/init | SDK gateway initialization |
/metrics | Prometheus 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
| Variable | Required | Description |
|---|---|---|
VITE_WEBSITE_URL | Yes | Dashboard URL, e.g. https://nativeblocks.io/dashboard |
VITE_API_ENDPOINT_GRAPHQL_URL | Yes | Core API GraphQL endpoint |
VITE_API_REALTIME_ENDPOINT_URL | Yes | Realtime WebSocket endpoint |
VITE_SENTRY_DSN | No | Sentry 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.iois reachable from the container (outbound HTTPS)LICENSE_KEYis 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
| Component | CPU | Memory |
|---|---|---|
| core-api | 2 vCPU | 2 GB |
| studio | 0.5 vCPU | 512 MB |
| postgres | 1 vCPU | 1 GB |
| valkey | 0.5 vCPU | 512 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_ENDPOINTinstead of the default cloud endpoint - Open the Studio at
studio.yourcompany.comand 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.


