Docker

bext ships as a single static binary, making it ideal for minimal Docker images. This page covers the official Dockerfile, docker-compose setup with Redis, and production hardening.

Official Dockerfile

Multi-stage build: compile in the Rust image, copy the binary to a minimal runtime image.

# ── Stage 1: Build ──────────────────────────────────────────────────
FROM rust:1.82-bookworm AS builder

WORKDIR /build
COPY . .

RUN cargo build --release --bin bext-server \
    --features "nginx-compat" \
    && strip target/release/bext-server

# ── Stage 2: Runtime ────────────────────────────────────────────────
FROM debian:bookworm-slim

RUN apt-get update && apt-get install -y --no-install-recommends \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# Non-root user
RUN groupadd -r bext && useradd -r -g bext -d /app -s /sbin/nologin bext

COPY --from=builder /build/target/release/bext-server /usr/local/bin/bext-server

WORKDIR /app
RUN chown bext:bext /app

USER bext

EXPOSE 3061
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \
    CMD ["bext-server", "health"]

ENTRYPOINT ["bext-server"]
CMD ["run"]

Alpine variant

For an even smaller image (~15MB), use Alpine as the runtime base. Note that you need to build with musl target:

FROM rust:1.82-bookworm AS builder
WORKDIR /build
COPY . .
RUN rustup target add x86_64-unknown-linux-musl \
    && cargo build --release --target x86_64-unknown-linux-musl --bin bext-server \
    && strip target/x86_64-unknown-linux-musl/release/bext-server

FROM alpine:3.20
RUN apk add --no-cache ca-certificates
RUN addgroup -S bext && adduser -S bext -G bext -h /app
COPY --from=builder /build/target/x86_64-unknown-linux-musl/release/bext-server /usr/local/bin/bext-server
WORKDIR /app
USER bext
EXPOSE 3061
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \
    CMD ["bext-server", "health"]
ENTRYPOINT ["bext-server"]
CMD ["run"]

Pre-built image

If you prefer not to build from source:

docker pull ghcr.io/bext-dev/bext-server:latest

Docker Compose

A production-ready compose file with bext and Redis:

# docker-compose.yml
services:
  bext:
    image: ghcr.io/bext-dev/bext-server:latest
    ports:
      - "80:3061"
      - "443:3061"
    volumes:
      - ./bext.config.toml:/app/bext.config.toml:ro
      - ./dist:/app/dist:ro
      - ./public:/app/public:ro
      - tls-certs:/var/lib/bext/certs
      - bext-cache:/var/cache/bext
    environment:
      - BEXT_LICENSE_KEY=${BEXT_LICENSE_KEY}
      - REDIS_URL=redis://redis:6379/0
      - BEXT_LOG_LEVEL=info
    depends_on:
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "bext-server", "health"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 10s
    deploy:
      resources:
        limits:
          cpus: "4"
          memory: 2G
        reservations:
          cpus: "1"
          memory: 512M
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    restart: unless-stopped

volumes:
  tls-certs:
  bext-cache:
  redis-data:

Start the stack:

docker compose up -d

Volume Mounts

Mount Purpose Mode
bext.config.toml Configuration file Read-only (:ro)
dist/ Built application (SSR bundle, static assets) Read-only
public/ Static public assets Read-only
tls-certs/ Auto-ACME certificate storage Read-write (bext writes certs here)
bext-cache/ Image optimization cache, ISR file cache Read-write

For Auto-TLS, the certificate volume must persist across container restarts. Without it, bext requests a new certificate on every startup, which may hit Let's Encrypt rate limits.

Environment Variable Passthrough

Pass environment variables to the container via environment or env_file:

# docker-compose.yml
services:
  bext:
    env_file:
      - .env.production
    environment:
      - BEXT_LICENSE_KEY=${BEXT_LICENSE_KEY}
      - BEXT_LOG_LEVEL=info

bext reads environment variables at startup. See the Environment Variables page for the complete list.

Health Check Configuration

The HEALTHCHECK directive in the Dockerfile uses bext-server health, which checks V8 pool, cache, Redis connectivity, and TLS certificate status. For Kubernetes, use an HTTP probe instead:

# Kubernetes liveness/readiness probe
livenessProbe:
  httpGet:
    path: /__bext/health
    port: 3061
  initialDelaySeconds: 10
  periodSeconds: 15
readinessProbe:
  httpGet:
    path: /__bext/health
    port: 3061
  initialDelaySeconds: 5
  periodSeconds: 10

Production Tips

Run as non-root. The Dockerfile above creates a bext user. Never run bext as root in production. If you need to bind to ports 80/443 inside the container, use --cap-add NET_BIND_SERVICE or map ports externally.

Set resource limits. Always set CPU and memory limits in compose or Kubernetes. bext's render workers respect the available CPU count, and the ISR cache respects max_entries. Without limits, a traffic spike could exhaust the host.

Use read-only filesystem. Mount the app directory and config as read-only. Only the TLS cert volume and cache volume need write access:

read_only: true
tmpfs:
  - /tmp
volumes:
  - tls-certs:/var/lib/bext/certs
  - bext-cache:/var/cache/bext

Pin image tags. Use a specific version tag instead of latest for reproducible deployments:

image: ghcr.io/bext-dev/bext-server:1.4.2

Log to stdout. Keep logging.output = "stdout" in Docker. Let Docker or your orchestrator handle log collection and rotation.