Podman / Docker Deployment

There is no public container registry for Mezite — no first-party image is published to pull. Building your own is a few lines, because the release archive already contains statically-linked binaries with no runtime dependencies. This page builds an image from the archive, then runs it with Podman (preferred) or Docker.

Building the Container Image

Download and verify the Linux archive for the architecture you are building for, following Installation — do that first, so the image is built from an archive whose signature you checked. Put the verified mezite-linux-amd64.tar.gz next to this Containerfile:

Containerfile docker
FROM docker.io/library/alpine:3.21

RUN apk --no-cache add ca-certificates tzdata

# A pinned numeric UID/GID lets a Kubernetes securityContext set runAsUser
# to a matching number.
RUN addgroup -S -g 10001 mezite \
    && adduser -S -u 10001 -G mezite mezite \
    && mkdir -p /var/lib/mezite \
    && chown -R mezite:mezite /var/lib/mezite

# The verified archive, sitting next to this file. It is flat and holds
# six entries: mezhub, mezd, msh, mezctl, LICENSE and README.md — so name
# the binaries explicitly rather than extracting the whole archive onto PATH.
ARG ARCH=amd64
COPY mezite-linux-${ARCH}.tar.gz /tmp/mezite.tar.gz
RUN tar -xzf /tmp/mezite.tar.gz --no-same-owner -C /usr/local/bin/ \
      mezhub mezd msh mezctl \
    && rm /tmp/mezite.tar.gz \
    && chmod 755 /usr/local/bin/mezhub /usr/local/bin/mezd \
       /usr/local/bin/msh /usr/local/bin/mezctl

USER mezite
EXPOSE 3025 3080 3023 3024
ENTRYPOINT ["mezhub"]
Build the image bash
podman build -t mezite:latest -f Containerfile .

# For arm64, verify and place mezite-linux-arm64.tar.gz instead, then:
#   podman build --build-arg ARCH=arm64 -t mezite:latest -f Containerfile .

The image carries all four binaries (mezhub, mezd, msh, mezctl), runs as unprivileged UID 10001, exposes the four standard ports, and has mezhub as its ENTRYPOINT — so arguments after the image name are appended to the mezhub command line.

Compose Deployment

The example below pairs mezhub with PostgreSQL; pass MEZITE_DB_DRIVER=sqlite (and drop the postgres service) for a zero-dependency setup, mounting a volume at /var/lib/mezite to persist the database file.

compose.yml — minimal mezhub + Postgres yaml
services:
  postgres:
    image: docker.io/library/postgres:16-alpine
    environment:
      POSTGRES_USER: mezite
      POSTGRES_PASSWORD: mezite
      POSTGRES_DB: mezite
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U mezite"]
      interval: 2s
      retries: 10

  mezhub:
    image: mezite:latest
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      MEZITE_CLUSTER_NAME: my-cluster
      MEZITE_DB_DRIVER: postgres
      MEZITE_DB_HOST: postgres
      MEZITE_DB_PORT: "5432"
      MEZITE_DB_USER: mezite
      MEZITE_DB_PASSWORD: mezite
      MEZITE_DB_NAME: mezite
      MEZITE_DB_SSLMODE: disable
      MEZITE_LOG_LEVEL: info
      MEZITE_PROXY_PUBLIC_ADDR: mezite.example.com:3080
      MEZITE_CA_KEY_PASSPHRASE: change-me-in-production
    ports:
      - "3025:3025"   # Auth gRPC
      - "3080:3080"   # Proxy HTTPS / Web UI
      - "3023:3023"   # SSH proxy
      - "3024:3024"   # Agent reverse tunnel
    healthcheck:
      test: ["CMD-SHELL", "/usr/local/bin/mezhub healthcheck"]
      interval: 2s
      retries: 15

volumes:
  pgdata:
Start with Compose bash
podman-compose -f compose.yml up -d

Running with podman run

Run mezhub directly bash
podman run -d --name mezhub \
  -e MEZITE_CLUSTER_NAME=my-cluster \
  -e MEZITE_DB_DRIVER=postgres \
  -e MEZITE_DB_HOST=host -e MEZITE_DB_PORT=5432 \
  -e MEZITE_DB_USER=mezite -e MEZITE_DB_PASSWORD=mezite \
  -e MEZITE_DB_NAME=mezite -e MEZITE_DB_SSLMODE=disable \
  -e MEZITE_CA_KEY_PASSPHRASE=change-me \
  -p 3025:3025 -p 3080:3080 -p 3023:3023 -p 3024:3024 \
  mezite:latest

Environment Variables

The mezhub server reads configuration in this order of precedence (highest wins): explicit environment variables, then the YAML config file (--config), then built-in defaults. The variables below are the env-var bindings that exist today; for keys without an env binding, use the YAML file. See Configuration for the full reference.

VariableDescriptionDefault
MEZITE_CLUSTER_NAMECluster identifier (also used as the trust domain)mezite
MEZITE_DB_DRIVERDatabase backend: sqlite or postgressqlite
MEZITE_DB_URLConnection string (overrides the host/port/user/password fields)
MEZITE_DB_HOSTPostgreSQL hostlocalhost
MEZITE_DB_PORTPostgreSQL port5432
MEZITE_DB_USERPostgreSQL usermezite
MEZITE_DB_PASSWORDPostgreSQL password(empty)
MEZITE_DB_NAMEPostgreSQL database namemezite
MEZITE_DB_SSLMODEPostgreSQL SSL moderequire
MEZITE_LOG_LEVELLog level (debug, info, warn, error)info
MEZITE_LOG_FORMATLog format (json or console)json
MEZITE_PROXY_PUBLIC_ADDRPublic address clients reach the proxy at (used for WebAuthn RPID and host/SSH cert principals)(empty)
MEZITE_OIDC_ISSUER_URLOIDC issuer URL advertised at /.well-known/openid-configuration. Explicit — never inferred from the Host header or public_addr.(empty)
MEZITE_CA_KEY_PASSPHRASEPassphrase for the software-encrypted CA signing keys(empty — required for production)
MEZITE_SINGLE_PORTMultiplex all protocols on the proxy HTTPS port via ALPNfalse
MEZITE_GRPC_ALLOW_HTTPEnable h2c on the gRPC auth port (for reverse-proxy deployments)false
MEZITE_AUTH_H2CDisable TLS on the gRPC auth port entirely (LB terminates TLS)(unset)

Listen addresses (auth.listen_addr, proxy.listen_addr, proxy.ssh_listen_addr, proxy.tunnel_listen_addr) are configured via the YAML file — they do not have env-var bindings and default to 0.0.0.0 on ports 3025/3080/3023/3024 respectively.

Health Checks

The proxy serves a liveness endpoint at /healthz and a readiness endpoint at /readyz on the HTTPS port. The mezhub healthcheck subcommand wraps the same check and is what the Compose example above uses.

Health check in Compose yaml
healthcheck:
  test: ["CMD-SHELL", "/usr/local/bin/mezhub healthcheck"]
  interval: 10s
  timeout: 5s
  retries: 3