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 source | Image | Recommended network |
|---|---|---|
| Official mirror | hub.fnknock.cn/kcilnk/fn-knock:latest | Mainland China; latest syncs every 30 minutes |
| Docker Hub | kcilnk/fn-knock:latest | Networks 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 mode | Recommendation | Description |
|---|---|---|
| Host network | Recommended and default | Uses the host network directly so the container can detect real interfaces and IPv6 |
| Bridge network | Optional | Uses 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.
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_INSTALLThe 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.
docker version
docker compose versionThe 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
mkdir -p /opt/fn-knock-docker
cd /opt/fn-knock-docker
docker pull hub.fnknock.cn/kcilnk/fn-knock:latest03 Create .env
Save the following as /opt/fn-knock-docker/.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=Key settings:
| Setting | Default | Description |
|---|---|---|
FN_KNOCK_IMAGE | hub.fnknock.cn/kcilnk/fn-knock:latest | Follows latest by default; use the Docker Hub image or a fixed version tag when needed |
ADMIN_VIEW_PORT / GO_REPROXY_PORT | 7991 / 7999 | Host ports for the admin panel and public gateway |
FN_KNOCK_DOCKER_IPV4_SUBNET | 172.30.0.0/16 | Bridge mode only; change to another private CIDR if it conflicts |
FN_KNOCK_DOCKER_IPV6_SUBNET | fd42:fb33:7f7a:100::/64 | Bridge mode only; IPv6 ULA /64 for the Docker bridge |
DOCKER_ADMIN_TRUSTED_PROXY_CIDRS | Empty | Set a proxy egress IP or CIDR only when 7991 is behind a trusted reverse proxy |
DOCKER_DISCOVER_LAN_IP | Empty | Set 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:
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:
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:
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
docker compose up -d
docker compose ps
docker compose logs -f fn-knockThe 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.
| Port | Service | Exposure | Purpose |
|---|---|---|---|
7991 | Admin panel | Host network | Set the Docker admin-panel password on your first visit |
7999 | Gateway / proxy entry | Host network | Used by external clients to reach proxied services |
7998 | Rust backend | Host loopback / internal | Normally leave unchanged |
7997 | Authentication frontend | Host loopback / internal | Normally leave unchanged |
7996 | Go gateway administration | Host loopback / internal | Normally leave unchanged |
- Open
http://<host-ip>:7991, set the Docker admin-panel password, and sign in. - Configure reverse proxies, subdomains, certificates, and authentication in the admin panel.
- Send external application traffic to the gateway on port
7999. - If
7991is behind a trusted reverse proxy, setDOCKER_ADMIN_TRUSTED_PROXY_CIDRSin.env. - Set
DOCKER_DISCOVER_LAN_IPonly 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.
cd /opt/fn-knock-docker
docker compose pull
docker compose up -d
docker compose psAutomate 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.
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:
docker ps --filter name=watchtower
docker logs watchtowerThis 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:
cd /opt/fn-knock-docker && docker compose exec -T fn-knock fn-knock-reset-panel-passwordYour 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.
