---
title: "Docker Deployment"
---

# Docker Deployment

This guide covers building and running HeliosProxy in Docker, including Docker Compose files with a PostgreSQL backend.

---

## Docker Image

### Building the Image

Create a multi-stage Dockerfile for a minimal production image:

```dockerfile
# Stage 1: Build
FROM rust:1.86-bookworm AS builder

WORKDIR /app
COPY Cargo.toml Cargo.lock ./
COPY src/ src/

# Build with desired features
ARG FEATURES="all-features,postgres-topology"
RUN cargo build --release --features "${FEATURES}"

# Stage 2: Runtime
FROM debian:bookworm-slim

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

RUN useradd --system --no-create-home heliosproxy

COPY --from=builder /app/target/release/heliosdb-proxy /usr/local/bin/heliosdb-proxy

RUN mkdir -p /etc/helios && chown heliosproxy:heliosproxy /etc/helios

USER heliosproxy

EXPOSE 6432 9090

HEALTHCHECK --interval=5s --timeout=3s --retries=3 \
    CMD curl -f http://localhost:9090/health || exit 1

ENTRYPOINT ["heliosdb-proxy"]
CMD ["--config", "/etc/helios/proxy.toml"]
```

This sample reads its configuration from `/etc/helios/proxy.toml`, the same path as the
published image, so every run and Compose example below works with either image.

Build the image:

```bash
docker build -t hdb-heliosdb-proxy:local .

# Build with specific features
docker build --build-arg FEATURES="pool-modes,postgres-topology" \
    -t hdb-heliosdb-proxy:ha .
```

### Image Variants

Local build tags (the published image corresponds to the all-features build):

| Tag | Features | Size |
|-----|----------|------|
| `local` | All features + PostgreSQL topology | ~35 MB |
| `ha` | Pool modes + PostgreSQL topology (`postgres` / `patroni` providers) | ~25 MB |
| `minimal` | Pool modes only | ~20 MB |

Transaction Replay — in-session recovery (`tr_mode`), the recovery journal
(`[journal]`) and the operator-driven `POST /api/replay` / `POST /api/shadow`
endpoints — is part of the default build and is present in every variant,
`minimal` included; `tr_enabled` is the runtime switch. The `ha-tr` cargo feature is a
deprecated no-op kept so existing feature lists still resolve. See
[Feature Flags](/docs/proxy/feature-flags/).

The published image is `ghcr.io/heliosdatabase/hdb-heliosdb-proxy`. Its tags follow the
releases (each release version, its major.minor line, and `latest`); the examples on this
page use `latest`, and production deployments should pin a release tag. It starts
`heliosdb-proxy --config /etc/helios/proxy.toml` and contains no configuration file, so
mount yours at that path (or pass your own arguments after the image name). If you set `[journal] dir`, mount a
persistent volume there so committed history survives container restarts.

---

## Running with Docker

### Quick Start

```bash
# Run with a configuration file (the image reads /etc/helios/proxy.toml)
docker run -d \
    --name heliosproxy \
    -p 6432:6432 \
    -p 9090:9090 \
    -v $(pwd)/proxy.toml:/etc/helios/proxy.toml:ro \
    ghcr.io/heliosdatabase/hdb-heliosdb-proxy:latest

# Run with command-line arguments (these replace the image's --config argument)
docker run -d \
    --name heliosproxy \
    -p 6432:6432 \
    ghcr.io/heliosdatabase/hdb-heliosdb-proxy:latest \
    --listen 0.0.0.0:6432 \
    --admin 127.0.0.1:9090 \
    --primary db-primary:5432 \
    --standby db-standby-1:5432
```

The admin API runs privileged operations, so the proxy refuses to start with a
non-loopback `--admin` address unless an admin token is set. This command-line example
keeps the admin API on the container's loopback (reach it with `docker exec`). To publish
port 9090, use a configuration file that sets `admin_address = "0.0.0.0:9090"` and
`admin_token`, which also keeps the secret out of the process arguments.

### Environment Variables

The proxy reads its settings from the configuration file and the command-line flags
only; it has no `HELIOS_PROXY_*` override variables. Inside the configuration file,
`${NAME}` is replaced with the value of the environment variable `NAME` (startup fails,
naming the variable, if it is unset) and `${NAME:-default}` falls back to `default`:

```toml
listen_address = "0.0.0.0:6432"
admin_address = "0.0.0.0:9090"
admin_token = "${HELIOS_ADMIN_TOKEN}"

[pool_mode]
mode = "${POOL_MODE:-transaction}"
max_pool_size = ${POOL_MAX:-200}
```

```bash
docker run -d \
    --name heliosproxy \
    -p 6432:6432 \
    -p 9090:9090 \
    -e HELIOS_ADMIN_TOKEN=replace-with-a-secret \
    -e POOL_MAX=200 \
    -e RUST_LOG=heliosdb_proxy=info \
    -v $(pwd)/proxy.toml:/etc/helios/proxy.toml:ro \
    ghcr.io/heliosdatabase/hdb-heliosdb-proxy:latest
```

`RUST_LOG`, when set, takes precedence over `--log-level`.

### Health Checks

`/health`, `/healthz`, `/livez` and `/readyz` answer without the admin token; every
other admin route needs `Authorization: Bearer <admin_token>` when a token is set.

```bash
# Liveness
docker exec heliosproxy curl -s http://localhost:9090/health

# Readiness (200 when at least one backend is healthy, 503 otherwise)
docker exec heliosproxy curl -s http://localhost:9090/readyz

# Node status (token required when admin_token is set)
docker exec heliosproxy curl -s -H "Authorization: Bearer $ADMIN_TOKEN" http://localhost:9090/nodes
```

---

## Docker Compose: Single Proxy with PostgreSQL

A basic setup with one proxy in front of a PostgreSQL primary.

```yaml
version: "3.8"

services:
  heliosproxy:
    image: ghcr.io/heliosdatabase/hdb-heliosdb-proxy:latest
    ports:
      - "6432:6432"
      - "9090:9090"
    volumes:
      - ./config/proxy.toml:/etc/helios/proxy.toml:ro
    depends_on:
      pg-primary:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9090/health"]
      interval: 5s
      timeout: 3s
      retries: 3
    restart: unless-stopped
    networks:
      - db-network

  pg-primary:
    image: postgres:16
    environment:
      POSTGRES_USER: helios
      POSTGRES_PASSWORD: helios
      POSTGRES_DB: appdb
    ports:
      - "5432:5432"
    volumes:
      - pg-primary-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U helios"]
      interval: 5s
      timeout: 3s
      retries: 5
    networks:
      - db-network

volumes:
  pg-primary-data:

networks:
  db-network:
    driver: bridge
```

Proxy configuration (`config/proxy.toml`):

```toml
listen_address = "0.0.0.0:6432"
admin_address = "0.0.0.0:9090"
# Required: a non-loopback admin bind without a token is refused at startup.
admin_token = "replace-with-a-secret"
tr_enabled = false
write_timeout_secs = 30

[pool_mode]
mode = "transaction"
max_pool_size = 100
min_idle = 5

[pool]
min_connections = 2
max_connections = 50
idle_timeout_secs = 300
max_lifetime_secs = 1800
acquire_timeout_secs = 30
test_on_acquire = true

[load_balancer]
read_write_split = false
read_strategy = "round_robin"
latency_threshold_ms = 100

[health]
check_interval_secs = 5
check_timeout_secs = 3
failure_threshold = 3
success_threshold = 2
check_query = "SELECT 1"

[[nodes]]
host = "pg-primary"
port = 5432
role = "primary"
weight = 100
enabled = true
name = "primary"
```

---

## Docker Compose: PostgreSQL with Proxy

A minimal variant for local testing, without a data volume or restart policy.
`config/pg-proxy.toml` takes the same keys as `config/proxy.toml` above:

```yaml
version: "3.8"

services:
  heliosproxy:
    image: ghcr.io/heliosdatabase/hdb-heliosdb-proxy:latest
    ports:
      - "6432:6432"
      - "9090:9090"
    volumes:
      - ./config/pg-proxy.toml:/etc/helios/proxy.toml:ro
    depends_on:
      - pg-primary
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9090/health"]
      interval: 5s
      timeout: 3s
      retries: 3
    networks:
      - pg-network

  pg-primary:
    image: postgres:16
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: apppass
      POSTGRES_DB: appdb
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser"]
      interval: 5s
      timeout: 3s
      retries: 5
    networks:
      - pg-network

networks:
  pg-network:
```

---

## Resource Limits

Set resource constraints for production deployments:

```yaml
services:
  heliosproxy:
    image: ghcr.io/heliosdatabase/hdb-heliosdb-proxy:latest
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 512M
        reservations:
          cpus: "0.5"
          memory: 128M
    ulimits:
      nofile:
        soft: 65536
        hard: 65536
```

---

## Logging

### View Proxy Logs

```bash
# Follow logs
docker compose logs -f heliosproxy

# View last 200 lines
docker compose logs --tail 200 heliosproxy

# Filter by log level (requires RUST_LOG=debug)
docker compose logs heliosproxy 2>&1 | grep -i error
```

### Enable Debug Logging

```yaml
services:
  heliosproxy:
    environment:
      RUST_LOG: heliosdb_proxy=debug
```

### Enable JSON Structured Logging

```yaml
services:
  heliosproxy:
    command: ["--config", "/etc/helios/proxy.toml", "--json-logs"]
```

---

## See Also

- [Standalone Deployment](/docs/proxy/deployment/standalone/)
- [Kubernetes Deployment](/docs/proxy/deployment/kubernetes/)
- [Configuration Reference](/docs/proxy/configuration/)
