Self-hosting NervesHub

Run your own NervesHub: architecture, Docker, PostgreSQL, object storage, optional ClickHouse, and the core environment variables.

Written By Josh Kalderimis

Last updated About 2 months ago

NervesHub is designed to be self-hosted. This guide covers what it needs to run, how the pieces fit together, and the core configuration to get an instance online.

Prefer not to run infrastructure yourself? Use the hosted NervesCloud service instead β€” see NervesHub vs NervesCloud.

Architecture at a glance

A NervesHub deployment is made of a few parts:

  • Application server β€” the Elixir/Phoenix app. It serves the web UI and HTTP API, and terminates device websocket connections. Pre-built Docker images are published to the GitHub Container Registry.

  • PostgreSQL β€” the primary database. NervesHub is developed against PostgreSQL 18.

  • S3-compatible object storage β€” stores firmware and archive files.

  • ClickHouse (optional) β€” stores device logs and powers analytics/Insights. Leave it out and the rest of the platform runs exactly the same.

The app exposes two endpoints: a web endpoint for users (web UI + HTTP API) and a device endpoint for device connections. These can run on different hosts and ports, and the device endpoint typically uses mutual TLS for certificate-based device auth.

Local trial with Docker Compose

The repository includes a docker-compose.yml that starts the backing services (PostgreSQL and ClickHouse) for local development:

services:
  postgres:
    image: postgres:18
    restart: always
    environment:
      POSTGRES_PASSWORD: postgres
    ports:
      - 5432:5432
  clickhouse:
    image: clickhouse/clickhouse-server:25.4.2.31
    environment:
      CLICKHOUSE_SKIP_USER_SETUP: 1
    ports:
      - "8123:8123"
      - "9000:9000"

This gives you the databases; you then run the application server (from a released Docker image or from source) pointed at them. For anything beyond a local trial, run PostgreSQL, object storage, and ClickHouse as managed/persistent services rather than throwaway containers.

Core configuration

NervesHub is configured entirely through environment variables at runtime. The most important ones are below. See the Self-hosting configuration reference for the complete list.

Secrets

Variable

Purpose

SECRET_KEY_BASE

Base secret for signing/encryption. Generate a long random value.

LIVE_VIEW_SIGNING_SALT

Salt for LiveView session signing.

Web endpoint

Variable

Purpose

HOST / WEB_HOST

Public hostname for the web UI and HTTP API.

HTTP_PORT / WEB_PORT / PORT

Ports the web endpoint listens on.

SESSION_COOKIE_DOMAIN

Cookie domain for sessions.

Device endpoint

Variable

Purpose

DEVICE_HOST

Hostname devices connect to.

DEVICE_PORT

Port for device connections.

DEVICES_WEBSOCKET_HOST

Websocket host advertised to devices.

DEVICE_SHARED_SECRETS_ENABLED

Enable shared-secret device authentication.

DEVICE_SSL_CERTFILE / DEVICE_SSL_KEYFILE

TLS cert/key for the device endpoint (mTLS).

DEVICE_SSL_CACERTFILE

CA bundle used to verify device certificates.

Database

Variable

Purpose

DATABASE_URL

PostgreSQL connection URL.

DATABASE_SSL

Enable TLS to the database.

DATABASE_AUTO_MIGRATOR

Run migrations automatically on boot.

DATABASE_POOL_SIZE

Connection pool size.

Firmware & archive storage

Variable

Purpose

FIRMWARE_UPLOAD_BACKEND

Storage backend (S3-compatible or local).

S3_BUCKET_NAME

Bucket for firmware/archives.

S3_REGION / S3_HOST

Object storage region/endpoint.

S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY

Object storage credentials.

FIRMWARE_UPLOAD_PATH

Local path when using the local backend.

FIRMWARE_UPLOAD_MAX_SIZE / ARCHIVE_UPLOAD_MAX_SIZE

Upload size limits.

Optional: device logs & analytics (ClickHouse)

Variable

Purpose

CLICKHOUSE_URL

ClickHouse connection URL. Omit to run without logs/Insights.

ANALYTICS_AUTO_MIGRATOR

Run ClickHouse migrations automatically.

Optional: email, auth, and observability

Variable

Purpose

SMTP_SERVER / SMTP_PORT / SMTP_USERNAME / SMTP_PASSWORD

Outbound email.

GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET

Google OAuth sign-in.

SENTRY_DSN_URL

Error reporting to Sentry.

OTLP_ENDPOINT

OpenTelemetry export.

STATSD_HOST / STATSD_PORT

StatsD metrics.

LOGO_URL_LIGHT / LOGO_URL_DARK

Branding.

The exact required vs. optional set can change between releases. Always cross-check against config/runtime.exs in the version you're deploying.

First run

  1. Provision PostgreSQL and (optionally) ClickHouse and object storage.

  2. Set the environment variables above.

  3. Start the application image. With DATABASE_AUTO_MIGRATOR enabled, the schema is created on boot; otherwise run migrations manually.

  4. Open the web endpoint, create your first user, organization, and product, then follow the Quickstart from step 2 onward β€” pointing your device and CLI at your own host instead of NervesCloud.

Production considerations

  • TLS everywhere. Terminate TLS on both endpoints. The device endpoint needs a certificate chain compatible with your device certificate strategy.

  • Persistent storage. Back PostgreSQL, ClickHouse, and object storage with durable, backed-up storage.

  • Clustering & the orchestrator. NervesHub's deployment orchestrator is designed to run across a cluster; review the deployment-tuning variables in the configuration reference before scaling out.

  • Draining connections. Device socket drainer settings control how connections are shed during deploys β€” tune them for your fleet size.

Related