# syntax=docker/dockerfile:1.7 ARG ALPINE_VERSION=3 FROM alpine:${ALPINE_VERSION} LABEL org.opencontainers.image.title="ferrite" \ org.opencontainers.image.description="Self-hosted DNS that blocks ads & trackers and routes chosen domains or devices through WireGuard, SOCKS5/Tor, or DPI evasion" \ org.opencontainers.image.source="https://github.com/koroblabs/ferrite-server" \ org.opencontainers.image.licenses="MIT" RUN apk add --no-cache ca-certificates curl libcap su-exec tar tini && \ addgroup -S -g 10001 ferrite && \ adduser -S -D -u 10001 -G ferrite -h /var/lib/ferrite -s /sbin/nologin ferrite && \ mkdir -p /etc/ferrite /var/lib/ferrite/bin /var/lib/ferrite/.local/share/ferrite && \ ln -s /var/lib/ferrite/bin/ferrite /usr/local/bin/ferrite && \ chown -R ferrite:ferrite /etc/ferrite /var/lib/ferrite COPY docker/config.toml /etc/ferrite/config.toml COPY docker/entrypoint.sh /usr/local/bin/ferrite-entrypoint RUN chmod 0755 /usr/local/bin/ferrite-entrypoint && \ chown ferrite:ferrite /etc/ferrite/config.toml ENV HOME=/var/lib/ferrite \ RUST_LOG=ferrite=info VOLUME ["/var/lib/ferrite"] # Docker exposes protocols separately; Ferrite still binds one DNS address. # 443 is the HTTPS (SNI) intercept for selective routing — only bound while # routing is enabled. EXPOSE 53/tcp 53/udp 80/tcp 443/tcp # Liveness probe against the always-public auth endpoint (returns 200 even when # no auth is configured). The container always binds the API on port 80 # internally, so 127.0.0.1:80 is correct regardless of host port mapping. # start-period is generous because the first start downloads the server and web # release assets before the API comes up. HEALTHCHECK --interval=30s --timeout=5s --start-period=90s --retries=3 \ CMD curl -fsS http://127.0.0.1:80/api/auth >/dev/null 2>&1 || exit 1 ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/ferrite-entrypoint"]