Skip to content

Deploy with Docker Compose

Choose the image source that works best on your network, then use the complete Compose configuration to run fn-knock on a Linux host or Linux-based NAS.

Open the original Docker Hub page

Image source

Image sourceImageRecommended network
Official mirrorhub.fnknock.cn/kcilnk/fn-knock:latestMainland China; latest syncs every 30 minutes
Docker Hubkcilnk/fn-knock:latestNetworks with reliable Docker Hub access

The examples below use the official mirror. To switch sources, replace the image in the pull command and FN_KNOCK_IMAGE in .env. To pin a release, replace latest with a published version tag.

Network mode

Network modeRecommendationDescription
Host networkRecommended and defaultUses the host network directly so the container can detect real interfaces and IPv6
Bridge networkOptionalUses an isolated dual-stack bridge and mapped ports, but DDNS may not find the host interfaces or IPv6

Use host networking when DDNS must obtain an address from a network interface or when the deployment depends on host IPv6. Choose the bridge only when stronger isolation matters more and host-interface detection is not required.

One-paste install

Paste the complete script below into a root terminal on the target Linux host. It uses the recommended host network by default, writes the complete Compose configuration, and starts fn-knock.

bash
sh <<'FN_KNOCK_INSTALL'
set -eu

if [ "$(id -u)" -ne 0 ]; then
  echo "Please run this installer in a root terminal." >&2
  exit 1
fi

command -v docker >/dev/null 2>&1 || { echo "Docker is not installed." >&2; exit 1; }
docker compose version >/dev/null 2>&1 || { echo "Docker Compose is not available." >&2; exit 1; }

install_dir=/opt/fn-knock-docker
mkdir -p "$install_dir"
cd "$install_dir"

if [ -e .env ] || [ -e docker-compose.yml ]; then
  echo "Existing .env or docker-compose.yml found; installation stopped." >&2
  exit 1
fi

cat > .env <<'FN_KNOCK_ENV'
FN_KNOCK_IMAGE=hub.fnknock.cn/kcilnk/fn-knock:latest
TZ=Asia/Shanghai
ADMIN_VIEW_PORT=7991
BACKEND_PORT=7998
AUTH_PORT=7997
GO_BACKEND_PORT=7996
GO_REPROXY_PORT=7999
DOCKER_ADMIN_TRUSTED_PROXY_CIDRS=
DOCKER_DISCOVER_LAN_IP=
FN_KNOCK_ENV

cat > docker-compose.yml <<'FN_KNOCK_COMPOSE'
services:
  fn-knock:
    image: ${FN_KNOCK_IMAGE}
    restart: unless-stopped
    network_mode: host
    environment:
      TZ: ${TZ:-Asia/Shanghai}
      FN_KNOCK_RUNTIME_TARGET: docker
      FN_KNOCK_DATA_DIR: /var/lib/fn-knock
      FN_KNOCK_GATEWAY_CONFIG_DIR: /usr/local/etc/fn-knock
      ADMIN_VIEW_PORT: ${ADMIN_VIEW_PORT:-7991}
      BACKEND_PORT: ${BACKEND_PORT:-7998}
      AUTH_PORT: ${AUTH_PORT:-7997}
      GO_BACKEND_PORT: ${GO_BACKEND_PORT:-7996}
      GO_REPROXY_PORT: ${GO_REPROXY_PORT:-7999}
      DOCKER_ADMIN_TRUSTED_PROXY_CIDRS: ${DOCKER_ADMIN_TRUSTED_PROXY_CIDRS:-}
      DOCKER_DISCOVER_LAN_IP: ${DOCKER_DISCOVER_LAN_IP:-}
      ADMIN_VIEW_HOST: 0.0.0.0
      BACKEND_HOST: 127.0.0.1
    volumes:
      - fn_knock_data:/var/lib/fn-knock
      - fn_knock_gateway:/usr/local/etc/fn-knock
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "curl -fsS http://127.0.0.1:${ADMIN_VIEW_PORT:-7991}/api/admin/healthz || exit 1",
        ]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 20s

volumes:
  fn_knock_data:
  fn_knock_gateway:
FN_KNOCK_COMPOSE

docker compose pull
docker compose up -d
docker compose ps
FN_KNOCK_INSTALL

The install directory is /opt/fn-knock-docker. If .env or docker-compose.yml already exists there, the script stops without overwriting it.

Complete installation

01 Check your Docker environment

A Linux host, Docker Engine, and Docker Compose are required.

bash
docker version
docker compose version

The steps below use host networking by default. This mode declares neither ports nor a custom bridge; services listen directly on host ports.

02 Prepare a directory and pull the image

bash
mkdir -p /opt/fn-knock-docker
cd /opt/fn-knock-docker
docker pull hub.fnknock.cn/kcilnk/fn-knock:latest

03 Create .env

Save the following as /opt/fn-knock-docker/.env:

dotenv
FN_KNOCK_IMAGE=hub.fnknock.cn/kcilnk/fn-knock:latest
TZ=Asia/Shanghai
ADMIN_VIEW_PORT=7991
BACKEND_PORT=7998
AUTH_PORT=7997
GO_BACKEND_PORT=7996
GO_REPROXY_PORT=7999
DOCKER_ADMIN_TRUSTED_PROXY_CIDRS=
DOCKER_DISCOVER_LAN_IP=

Key settings:

SettingDefaultDescription
FN_KNOCK_IMAGEhub.fnknock.cn/kcilnk/fn-knock:latestFollows latest by default; use the Docker Hub image or a fixed version tag when needed
ADMIN_VIEW_PORT / GO_REPROXY_PORT7991 / 7999Host ports for the admin panel and public gateway
FN_KNOCK_DOCKER_IPV4_SUBNET172.30.0.0/16Bridge mode only; change to another private CIDR if it conflicts
FN_KNOCK_DOCKER_IPV6_SUBNETfd42:fb33:7f7a:100::/64Bridge mode only; IPv6 ULA /64 for the Docker bridge
DOCKER_ADMIN_TRUSTED_PROXY_CIDRSEmptySet a proxy egress IP or CIDR only when 7991 is behind a trusted reverse proxy
DOCKER_DISCOVER_LAN_IPEmptySet only when a third-party reverse proxy cannot detect the host LAN address automatically

04 Create docker-compose.yml

The recommended configuration uses one fn-knock container with host networking so it can access the host's real interfaces and IPv6:

yaml
services:
  fn-knock:
    image: ${FN_KNOCK_IMAGE}
    restart: unless-stopped
    network_mode: host
    environment:
      TZ: ${TZ:-Asia/Shanghai}
      FN_KNOCK_RUNTIME_TARGET: docker
      FN_KNOCK_DATA_DIR: /var/lib/fn-knock
      FN_KNOCK_GATEWAY_CONFIG_DIR: /usr/local/etc/fn-knock
      ADMIN_VIEW_PORT: ${ADMIN_VIEW_PORT:-7991}
      BACKEND_PORT: ${BACKEND_PORT:-7998}
      AUTH_PORT: ${AUTH_PORT:-7997}
      GO_BACKEND_PORT: ${GO_BACKEND_PORT:-7996}
      GO_REPROXY_PORT: ${GO_REPROXY_PORT:-7999}
      DOCKER_ADMIN_TRUSTED_PROXY_CIDRS: ${DOCKER_ADMIN_TRUSTED_PROXY_CIDRS:-}
      DOCKER_DISCOVER_LAN_IP: ${DOCKER_DISCOVER_LAN_IP:-}
      ADMIN_VIEW_HOST: 0.0.0.0
      BACKEND_HOST: 127.0.0.1
    volumes:
      - fn_knock_data:/var/lib/fn-knock
      - fn_knock_gateway:/usr/local/etc/fn-knock
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "curl -fsS http://127.0.0.1:${ADMIN_VIEW_PORT:-7991}/api/admin/healthz || exit 1",
        ]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 20s

volumes:
  fn_knock_data:
  fn_knock_gateway:

Optional: switch to bridge networking

A bridge can prevent DDNS from finding the host's interfaces or IPv6. After confirming that the deployment does not depend on “from interface,” replace .env with:

dotenv
FN_KNOCK_IMAGE=hub.fnknock.cn/kcilnk/fn-knock:latest
TZ=Asia/Shanghai
ADMIN_VIEW_PORT=7991
BACKEND_PORT=7998
AUTH_PORT=7997
GO_BACKEND_PORT=7996
GO_REPROXY_PORT=7999
FN_KNOCK_DOCKER_IPV4_SUBNET=172.30.0.0/16
FN_KNOCK_DOCKER_IPV6_SUBNET=fd42:fb33:7f7a:100::/64
DOCKER_ADMIN_TRUSTED_PROXY_CIDRS=
DOCKER_DISCOVER_LAN_IP=

Then replace docker-compose.yml with:

yaml
services:
  fn-knock:
    image: ${FN_KNOCK_IMAGE}
    restart: unless-stopped
    environment:
      TZ: ${TZ:-Asia/Shanghai}
      FN_KNOCK_RUNTIME_TARGET: docker
      FN_KNOCK_DATA_DIR: /var/lib/fn-knock
      FN_KNOCK_GATEWAY_CONFIG_DIR: /usr/local/etc/fn-knock
      ADMIN_VIEW_PORT: ${ADMIN_VIEW_PORT:-7991}
      BACKEND_PORT: ${BACKEND_PORT:-7998}
      AUTH_PORT: ${AUTH_PORT:-7997}
      GO_BACKEND_PORT: ${GO_BACKEND_PORT:-7996}
      GO_REPROXY_PORT: ${GO_REPROXY_PORT:-7999}
      DOCKER_ADMIN_TRUSTED_PROXY_CIDRS: ${DOCKER_ADMIN_TRUSTED_PROXY_CIDRS:-}
      DOCKER_DISCOVER_LAN_IP: ${DOCKER_DISCOVER_LAN_IP:-}
      ADMIN_VIEW_HOST: 0.0.0.0
      BACKEND_HOST: 127.0.0.1
    ports:
      - "${ADMIN_VIEW_PORT:-7991}:${ADMIN_VIEW_PORT:-7991}"
      - "${GO_REPROXY_PORT:-7999}:${GO_REPROXY_PORT:-7999}"
    networks:
      - fn_knock_net
    volumes:
      - fn_knock_data:/var/lib/fn-knock
      - fn_knock_gateway:/usr/local/etc/fn-knock
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "curl -fsS http://127.0.0.1:${ADMIN_VIEW_PORT:-7991}/api/admin/healthz || exit 1",
        ]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 20s

volumes:
  fn_knock_data:
  fn_knock_gateway:

networks:
  fn_knock_net:
    enable_ipv6: true
    ipam:
      config:
        - subnet: ${FN_KNOCK_DOCKER_IPV4_SUBNET:-172.30.0.0/16}
        - subnet: ${FN_KNOCK_DOCKER_IPV6_SUBNET:-fd42:fb33:7f7a:100::/64}

05 Start and verify

bash
docker compose up -d
docker compose ps
docker compose logs -f fn-knock

The final command follows the logs. Press Ctrl+C to stop following them.

First access and setup

The default host mode uses the host network namespace directly. The admin panel listens on 7991, the gateway on 7999, and the remaining services stay internal or on host loopback.

PortServiceExposurePurpose
7991Admin panelHost networkSet the Docker admin-panel password on your first visit
7999Gateway / proxy entryHost networkUsed by external clients to reach proxied services
7998Rust backendHost loopback / internalNormally leave unchanged
7997Authentication frontendHost loopback / internalNormally leave unchanged
7996Go gateway administrationHost loopback / internalNormally leave unchanged
  1. Open http://<host-ip>:7991, set the Docker admin-panel password, and sign in.
  2. Configure reverse proxies, subdomains, certificates, and authentication in the admin panel.
  3. Send external application traffic to the gateway on port 7999.
  4. If 7991 is behind a trusted reverse proxy, set DOCKER_ADMIN_TRUSTED_PROXY_CIDRS in .env.
  5. Set DOCKER_DISCOVER_LAN_IP only when a third-party reverse proxy cannot detect the host LAN address automatically.

Update to the latest image

Keep latest in .env, then pull and recreate the container. Persistent volumes are preserved.

bash
cd /opt/fn-knock-docker
docker compose pull
docker compose up -d
docker compose ps

Automate updates with Watchtower

When .env uses latest, you can run Watchtower on the same Docker host. By default, it checks all running containers every 24 hours. When the digest behind an image tag changes, Watchtower pulls the new image and recreates the container with its existing configuration. The fn-knock mounted volumes are preserved, but the update causes a brief restart. A fixed version tag is not automatically changed to a different tag.

bash
docker run -d \
  --name watchtower \
  --restart unless-stopped \
  -v /var/run/docker.sock:/var/run/docker.sock \
  nickfedor/watchtower --cleanup

--cleanup removes superseded images after a container is updated successfully. Without this option, Watchtower does not clean up old images by default, so dangling images with a repository and tag shown as <none> may remain. This option does not remove the fn-knock named data volumes.

Confirm that Watchtower is running and inspect its check history:

bash
docker ps --filter name=watchtower
docker logs watchtower

This basic configuration manages every running container on the host and receives highly privileged access to Docker through the Docker socket. Use it only on a trusted host, and back up fn-knock before enabling it. If other containers must not be updated automatically, follow the official Watchtower documentation to limit the update scope with container names, labels, or a scope.

Reset the admin-panel password

If you forget the password, sign in to the Docker host and run:

bash
cd /opt/fn-knock-docker && docker compose exec -T fn-knock fn-knock-reset-panel-password

Your next visit to port 7991 returns to the first-time password setup flow. This command clears only the admin-panel password, login sessions, and failed-login backoff state. It does not remove application settings, proxy rules, certificates, allowlists, logs, or data volumes.

Continue reading

Community QQ group: 1081609274