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:
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"] 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.
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: podman-compose -f compose.yml up -d Running with podman run
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.
| Variable | Description | Default |
|---|---|---|
MEZITE_CLUSTER_NAME | Cluster identifier (also used as the trust domain) | mezite |
MEZITE_DB_DRIVER | Database backend: sqlite or postgres | sqlite |
MEZITE_DB_URL | Connection string (overrides the host/port/user/password fields) | — |
MEZITE_DB_HOST | PostgreSQL host | localhost |
MEZITE_DB_PORT | PostgreSQL port | 5432 |
MEZITE_DB_USER | PostgreSQL user | mezite |
MEZITE_DB_PASSWORD | PostgreSQL password | (empty) |
MEZITE_DB_NAME | PostgreSQL database name | mezite |
MEZITE_DB_SSLMODE | PostgreSQL SSL mode | require |
MEZITE_LOG_LEVEL | Log level (debug, info, warn, error) | info |
MEZITE_LOG_FORMAT | Log format (json or console) | json |
MEZITE_PROXY_PUBLIC_ADDR | Public address clients reach the proxy at (used for WebAuthn RPID and host/SSH cert principals) | (empty) |
MEZITE_OIDC_ISSUER_URL | OIDC issuer URL advertised at /.well-known/openid-configuration. Explicit — never inferred from the Host header or
public_addr. | (empty) |
MEZITE_CA_KEY_PASSPHRASE | Passphrase for the software-encrypted CA signing keys | (empty — required for production) |
MEZITE_SINGLE_PORT | Multiplex all protocols on the proxy HTTPS port via ALPN | false |
MEZITE_GRPC_ALLOW_HTTP | Enable h2c on the gRPC auth port (for reverse-proxy deployments) | false |
MEZITE_AUTH_H2C | Disable 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.
healthcheck:
test: ["CMD-SHELL", "/usr/local/bin/mezhub healthcheck"]
interval: 10s
timeout: 5s
retries: 3