Docker
Since v0.22.0+Deploy Hot Updater server using Docker containers.
Prerequisites
- Docker Or OrbStack: Install it via Docker or OrbStack.
- Docker Compose (included with Docker Desktop)
- PostgreSQL database or use Docker Compose setup below
Installation
Install required dependencies.
npm install @hot-updater/server @hot-updater/aws hono @hono/node-server drizzle-orm postgresnpm install @hot-updater/bare @hot-updater/standalone hot-updater drizzle-kit tsx typescript @types/node --save-devDatabase Setup
Create the database connection file.
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "../hot-updater-schema";
const client = postgres(process.env.DATABASE_URL!);
export const db = drizzle(client, { schema, casing: "snake_case" });import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./hot-updater-schema.ts",
out: "./drizzle",
dialect: "postgresql",
dbCredentials: { url: process.env.DATABASE_URL! },
});Hot Updater Configuration
Create the Hot Updater instance.
import { createHotUpdater } from "@hot-updater/server";
import { drizzleAdapter } from "@hot-updater/server/adapters/drizzle";
import { s3Storage } from "@hot-updater/aws";
import { db } from "./drizzle";
export const hotUpdater = createHotUpdater({
database: drizzleAdapter({ db, provider: "postgresql" }),
storages: [
s3Storage({
region: "auto",
endpoint: process.env.R2_ENDPOINT!,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
bucketName: process.env.R2_BUCKET_NAME!,
}),
],
basePath: "/hot-updater",
routes: { updateCheck: true, bundles: true },
});Server Setup
Create the server entry point.
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { bearerAuth } from "hono/bearer-auth";
import { hotUpdater } from "./hotUpdater";
const managementToken = process.env.HOT_UPDATER_AUTH_TOKEN;
if (!managementToken) {
throw new Error("HOT_UPDATER_AUTH_TOKEN is required");
}
const app = new Hono();
app.use("/hot-updater/api/*", bearerAuth({ token: managementToken }));
app.mount("/hot-updater", hotUpdater.handler);
const port = Number(process.env.PORT) || 3000;
serve({ fetch: app.fetch, port }, (info) => {
console.log(`Server running at http://localhost:${info.port}`);
});When using Hono mount(), update-check routes such as
/hot-updater/app-version/* and /hot-updater/fingerprint/* are usually
exposed for React Native clients, but that is a deployment choice.
/hot-updater/version is always mounted for diagnostics. Always protect
/hot-updater/api/* when routes.bundles: true.
Schema Generation
Generate the Drizzle schema for Hot Updater tables.
npx hot-updater db generate src/hotUpdater.ts --yesdb generate writes hot-updater-schema.ts; it does not change the database.
Apply the generated schema separately:
npx drizzle-kit pushBuild Configuration
The image uses CommonJS and compiles the root schema together with src:
{
"private": true,
"type": "commonjs",
"scripts": {
"build": "tsc"
}
}{
"compilerOptions": {
"esModuleInterop": true,
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "dist",
"skipLibCheck": true,
"strict": true,
"target": "ES2022"
},
"include": ["src", "hot-updater-schema.ts"]
}This emits the server entry point as dist/src/index.js.
Dockerfile
Create Dockerfile for schema generation, migration, and production runtime.
FROM node:22-slim AS builder
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
COPY . .
RUN DATABASE_URL=postgresql://schema:placeholder@localhost:5432/schema \
npx hot-updater db generate src/hotUpdater.ts --yes
RUN npm run build
FROM builder AS migrator
CMD ["npx", "drizzle-kit", "push"]
FROM node:22-slim AS runtime
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/src/index.js"]Create .dockerignore:
node_modules
dist
.git
.env*
*.logBuild and Run
Build the migration and runtime images, then apply the schema before starting the server.
docker build --target migrator -t hot-updater-migrator .
docker run --rm \
-e DATABASE_URL="postgresql://user:password@host:5432/dbname" \
hot-updater-migrator
docker build --target runtime -t hot-updater-server .Run the container with environment variables.
docker run -p 3000:3000 \
-e DATABASE_URL="postgresql://user:password@host:5432/dbname" \
-e HOT_UPDATER_AUTH_TOKEN="replace-with-a-secret" \
-e R2_ENDPOINT="https://your-account-id.r2.cloudflarestorage.com" \
-e R2_ACCESS_KEY_ID="..." \
-e R2_SECRET_ACCESS_KEY="..." \
-e R2_BUCKET_NAME="..." \
hot-updater-serverDocker Compose
Create docker-compose.yml for complete setup with PostgreSQL.
version: "3.8"
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: hot_updater
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
migrate:
build:
context: .
target: migrator
environment:
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/hot_updater
depends_on:
postgres:
condition: service_healthy
restart: "no"
hot-updater:
build:
context: .
target: runtime
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/hot_updater
HOT_UPDATER_AUTH_TOKEN: replace-with-a-secret
PORT: 3000
R2_ENDPOINT: https://your-account-id.r2.cloudflarestorage.com
R2_ACCESS_KEY_ID: "..."
R2_SECRET_ACCESS_KEY: "..."
R2_BUCKET_NAME: "..."
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
volumes:
postgres_data:Run with Docker Compose
Build the targets, apply the schema once, then start the server.
docker compose build
docker compose run --rm migrate
docker compose up -d hot-updaterView logs.
docker compose logs -f hot-updaterStop all services.
docker compose downCLI Configuration
Configure your CLI to use this server.
import { bare } from "@hot-updater/bare";
import { s3Storage } from "@hot-updater/aws";
import { standaloneRepository } from "@hot-updater/standalone";
import { defineConfig } from "hot-updater";
const managementToken = process.env.HOT_UPDATER_AUTH_TOKEN;
if (!managementToken) {
throw new Error("HOT_UPDATER_AUTH_TOKEN is required");
}
export default defineConfig({
build: bare({ enableHermes: true }),
storage: s3Storage({
region: "auto",
endpoint: process.env.R2_ENDPOINT!,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
bucketName: process.env.R2_BUCKET_NAME!,
}),
database: standaloneRepository({
baseUrl: "http://localhost:3000/hot-updater",
commonHeaders: { Authorization: `Bearer ${managementToken}` },
}),
updateStrategy: "appVersion",
});The storage plugin must match the storages in your server's createHotUpdater.