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.