lightweight futu opend docker
Running on Kubernetes? See
k8s/README.mdfor a reference deployment plus first-run SMS/CAPTCHA delivery viakubectl.Using a coding agent (Claude Code, opencode, codex, …)? This repo ships an agent skill at
skills/futu-opend/SKILL.mdthat orchestrates install, 2FA delivery, and day-2 ops (restart, re-login, version bump, teardown) for compose /docker run/ k8s. Agents that readAGENTS.mdorCLAUDE.mdwill pick it up automatically.
# default base image is ubuntu
docker pull ghcr.io/manhinhang/futu-opend-docker:ubuntu-stable| Base Image | Tags |
|---|---|
| ubuntu | ubuntu-stable |
| ubuntu | ubuntu-beta |
| ubuntu | ubuntu-{opend_version} |
| centos | centos-stable |
| centos | centos-beta |
| centos | centos-{opend_version} |
Generate your own RSA key
openssl genrsa -out futu.pem 1024
# Compute the password MD5 so plaintext never lands in your shell history.
FUTU_ACCOUNT_PWD_MD5=$(echo -n '<your_password>' | md5sum | awk '{print $1}')
docker run -it --name futu-opend-docker \
-e FUTU_ACCOUNT_ID=<your_account_id> \
-e FUTU_ACCOUNT_PWD_MD5="$FUTU_ACCOUNT_PWD_MD5" \
-v $(pwd)/futu.pem:/.futu/futu.pem \
-v futu-opend-data:/home/futu/.com.futunn.FutuOpenD \
-p 11111:11111 \
-p 22222:22222 \
ghcr.io/manhinhang/futu-opend-docker
FUTU_ACCOUNT_PWD(plaintext) is still accepted as a legacy fallback — the container hashes it at runtime and emits a stderr deprecation warning. PreferFUTU_ACCOUNT_PWD_MD5to keep plaintext out of agent transcripts, shared shell sessions, anddocker compose configoutput.The
futu-opend-datanamed volume keeps FutuOpenD's login session across container recreates so SMS verification isn't required on every restart. See Login session persistence for details.Port mappings:
11111: API port for FutuOpenD protocol22222: Telnet port for 2FA input (optional, but recommended for automation)
FutuOpenD may require two types of verification:
- SMS verification code - sent to your phone
- Picture CAPTCHA - downloaded to container
You can input verification codes using either docker attach or telnet:
- Attach to futu opend container
docker attach futu-opend- Input verification code based on the type:
For SMS verification code:
input_phone_verify_code -code=<SMS_CODE>For picture CAPTCHA:
First, copy the CAPTCHA image from container:
docker cp futu-opend:/home/futu/.com.futunn.FutuOpenD/F3CNN/PicVerifyCode.png ./PicVerifyCode.pngThen view the image and input the code:
input_pic_verify_code -code=<CAPTCHA_CODE>Connect to the FutuOpenD telnet port (22222) and send the command:
For SMS verification code:
# Interactive
telnet localhost 22222
input_phone_verify_code -code=<SMS_CODE>
# One-liner
echo "input_phone_verify_code -code=<SMS_CODE>" | telnet localhost 22222For picture CAPTCHA:
First, extract the CAPTCHA image:
docker cp futu-opend:/home/futu/.com.futunn.FutuOpenD/F3CNN/PicVerifyCode.png ./PicVerifyCode.pngThen input the code via telnet:
echo "input_pic_verify_code -code=<CAPTCHA_CODE>" | telnet localhost 22222Automation script example:
#!/bin/bash
# Auto-input SMS verification code
{
sleep 2
echo "input_phone_verify_code -code=$1"
sleep 1
} | telnet localhost 22222#!/bin/bash
# Auto-input picture CAPTCHA
# First extract and display the image, then input the code
docker cp futu-opend:/home/futu/.com.futunn.FutuOpenD/F3CNN/PicVerifyCode.png /tmp/PicVerifyCode.png
# Display image (choose your preferred viewer)
open /tmp/PicVerifyCode.png # macOS
# xdg-open /tmp/PicVerifyCode.png # Linux
# start /tmp/PicVerifyCode.png # Windows
read -p "Enter CAPTCHA code: " captcha_code
{
sleep 2
echo "input_pic_verify_code -code=$captcha_code"
sleep 1
} | telnet localhost 22222Copy the tracked .env.example template to .env, then edit it (auto-loaded by docker compose):
cp .env.example .env| Environment Variable | Description |
|---|---|
| FUTU_ACCOUNT_ID | Futu account ID |
| FUTU_ACCOUNT_PWD_MD5 | Preferred. Futu account password MD5 hash. Compute with echo -n '<pwd>' | md5sum | awk '{print $1}'. |
| FUTU_ACCOUNT_PWD | Deprecated. Plaintext password — hashed at runtime by start.sh; ignored if FUTU_ACCOUNT_PWD_MD5 is set. Triggers a stderr deprecation warning when used. |
| FUTU_OPEND_IP | OpenD bind address inside the container (default: 0.0.0.0) |
| FUTU_OPEND_PORT | Futu OpenD API Port in container (default: 11111) |
| FUTU_OPEND_TELNET_PORT | Futu OpenD Telnet Port (default: 22222) |
| FUTU_OPEND_WEBSOCKET_PORT | Enable WebSocket listener on this port (default: disabled). |
| FUTU_OPEND_WEBSOCKET_IP | WebSocket bind address (default: 0.0.0.0 when FUTU_OPEND_WEBSOCKET_PORT is set, else not applied) |
| FUTU_OPEND_VER | OpenD version to build (compose build.args). Defaulted in .env.example; mirrors opend_version.json. |
Note: the compose file uses
network_mode: host(andbuild.network: host) so the container shares the host's network stack. Noports:mapping is needed; OpenD's listeners bind directly on the host. This avoids docker-bridge connectivity issues we hit with Futu's auth servers.
docker compose up -dMounting the futu-opend-data named volume at
/home/futu/.com.futunn.FutuOpenD lets FutuOpenD's runtime state —
device-whitelist token, login cache, captcha PNG, and other session
metadata — survive container recreate. The compose stack attaches it
automatically; bare docker run users add
-v futu-opend-data:/home/futu/.com.futunn.FutuOpenD. With the volume in
place, SMS verification is only required when the volume is empty
(first ever run, account switch, or explicit wipe).
Caveat — Futu-side whitelist lifetime: Futu's server-side device whitelist has a short shelf life (hours to days). When Futu invalidates the whitelist, the next login will prompt for SMS again regardless of what's in the volume. The volume eliminates Docker-recreate-induced fresh-device churn; it does not extend Futu's own whitelist policy.
Image version requirement: this volume needs an image built from a
Dockerfile that pre-creates /home/futu/.com.futunn.FutuOpenD with
futu ownership (introduced alongside this volume). Older published
images leave the mount point owned by root, and FutuOpenD (running as
futu) will EACCES on first write. Run docker compose pull (or
docker compose build) when adopting this change.
Wipe the volume to force a fresh login. The volume's actual name
depends on how you launched the stack (compose namespaces it by project
directory; docker run does not):
# Compose users — wipes everything in one step:
docker compose down -v
# Compose users — manual, while the stack is down:
docker volume rm futu-opend-docker_futu-opend-data
# Bare `docker run` users:
docker rm -f futu-opend-docker
docker volume rm futu-opend-dataRun docker volume ls if you're not sure which volume name applies.
Wipe when:
- Switching to a different
FUTU_ACCOUNT_ID. - After upgrading FutuOpenD across major versions.
- Diagnosing login loops that don't respond to credential rotation.
The container includes a healthcheck that monitors the FutuOpenD process:
| Setting | Value | Description |
|---|---|---|
| test | pgrep FutuOpenD |
Check FutuOpenD process is running |
| interval | 30s | Check every 30 seconds |
| timeout | 600s | Timeout for each check |
| retries | 3 | Mark unhealthy after 3 failures |
| start_period | 180s | Grace period for container startup |
Check container health status:
docker ps --format "table {{.Names}}\t{{.Status}}"Then enter verification codes when prompted:
Using docker attach:
docker attach futu-opend
# For SMS verification
input_phone_verify_code -code=<SMS_CODE>
# For picture CAPTCHA (extract image first)
docker cp futu-opend:/home/futu/.com.futunn.FutuOpenD/F3CNN/PicVerifyCode.png ./PicVerifyCode.png
input_pic_verify_code -code=<CAPTCHA_CODE>Using telnet (see Input verification codes section for full details):
# For SMS verification
echo "input_phone_verify_code -code=<SMS_CODE>" | telnet localhost 22222
# For picture CAPTCHA
docker cp futu-opend:/home/futu/.com.futunn.FutuOpenD/F3CNN/PicVerifyCode.png ./PicVerifyCode.png
echo "input_pic_verify_code -code=<CAPTCHA_CODE>" | telnet localhost 22222Note: Ubuntu builds require version 9.4.x or later with Ubuntu 18.04 base image. Ubuntu 16.04 builds are no longer provided by Futu.
- Use ubuntu as base image
docker build -t futu-opend-docker --build-arg FUTU_OPEND_VER=10.4.6408 --build-arg BASE_IMG=ubuntu .- Use centos as base image
docker build -t futu-opend-docker --build-arg FUTU_OPEND_VER=10.4.6408 --build-arg BASE_IMG=centos .If you encounter download failures during build:
- Network issues: The download script includes automatic retry logic (3 attempts)
- Version compatibility: Ensure you're using a version that has Ubuntu 18.04 builds (9.4.x or later)
- Check available versions: Visit Futu OpenD download page
If the container fails to start:
- RSA key: Ensure
futu.pemexists and is properly mounted at/.futu/futu.pem - Environment variables: Verify
FUTU_ACCOUNT_IDand eitherFUTU_ACCOUNT_PWD_MD5(preferred) or the deprecatedFUTU_ACCOUNT_PWDare set - Verification required: First run may require verification codes
- Stale session state: if you've changed accounts or upgraded OpenD across major versions, wipe the data volume — see Login session persistence.
FutuOpenD may prompt for two types of verification:
-
SMS verification code (
input_phone_verify_code)- Sent to your registered phone number
- Input via docker attach or telnet
-
Picture CAPTCHA (
input_pic_verify_code)- Downloaded to
/home/futu/.com.futunn.FutuOpenD/F3CNN/PicVerifyCode.pnginside container - Extract with:
docker cp futu-opend:/home/futu/.com.futunn.FutuOpenD/F3CNN/PicVerifyCode.png ./PicVerifyCode.png - View the image and input the code
- Downloaded to
Tip: Use telnet method for automation - see Input verification codes section for details.
A node:test suite that takes credentials from FUTU_ACCOUNT_ID plus FUTU_ACCOUNT_PWD_MD5 (preferred) or the deprecated FUTU_ACCOUNT_PWD, drives a real login (with SMS support), and asserts the OpenAPI WebSocket layer is up. Local-only — npm run test:e2e.
See docs/E2E.md for prerequisites, architecture, and troubleshooting.
This project is not affiliated with Futu Securities International (Hong Kong) Limited.
Good luck and enjoy.