Choose the all-in-one server image, not the panel-only image

MetaCubeXD has two Docker images that look similar but do different jobs. The standalone ghcr.io/metacubex/metacubexd:latest image serves only the web dashboard on container port 80. It does not bundle, start, or supervise Mihomo; use that image only when a separate Mihomo instance already exposes an external controller.

This guide uses the official all-in-one image, ghcr.io/metacubex/metacubexd-server:latest. The server image combines the dashboard, the same-origin control agent, and an architecture-matched bundled Mihomo kernel. Its profile manager can validate and activate profiles and supervise the kernel, so it is the correct image for the official Compose workflow described here.

Do not swap the image names: replacing metacubexd-server with metacubexd leaves you with a dashboard but no bundled kernel or control agent. A healthy panel-only container therefore does not prove that a proxy core exists.

The supplied Compose configuration is proxy-only by default. It publishes a mixed proxy port that applications can use explicitly; it does not turn the host into a transparent LAN gateway, and it does not enable TUN automatically.

Download the official Compose file and create separate secrets

Create a dedicated directory, download the Compose file from the MetaCubeXD repository, and keep the file together with a local .env. Review the downloaded YAML before starting it because the latest tag and the repository file can change after this article is published.

Shellmkdir metacubexd-server
cd metacubexd-server
curl -fLO https://raw.githubusercontent.com/MetaCubeX/metacubexd/main/docs/docker-compose.yml

The official file requires CONTROL_TOKEN and CLASH_SECRET. Put two different, strong random values in .env; do not reuse a subscription token, account password, or a value shown in an online example.

.envCONTROL_TOKEN=replace-with-a-unique-control-token
CLASH_SECRET=replace-with-a-different-clash-secret
TZ=Asia/Shanghai
VariableWhat it protectsImportant boundary
CONTROL_TOKENThe server agent routes under /api/controlThe same-origin page receives it, so it is not a login wall for the dashboard
CLASH_SECRETMihomo's Clash API on port 9090Enter this value as the dashboard endpoint Secret

Keep .env out of public repositories, screenshots, support bundles, and shell history. Anyone who receives these values may be able to control profiles or the Mihomo API within whatever network access your host permits.

Validate, pull, and start the service in a checkable order

Run Compose from the directory containing docker-compose.yml and .env. First ask Compose to resolve variables and validate the model without printing a rendered configuration that could expose secrets. Then pull the referenced image, start the service, inspect its state, and read the service log.

Shelldocker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 metacubexd
  1. config --quiet must exit successfully; a missing required variable stops the workflow here.
  2. pull fetches the image referenced by the current Compose file.
  3. up -d creates or updates the service without occupying the terminal.
  4. ps shows whether the container is running and whether its health check has settled.
  5. logs reveals agent startup, kernel startup, profile parsing, and port-collision errors.

The service name in the official file is metacubexd, even though its image is metacubexd-server. Use the service name with docker compose logs. If startup fails, preserve the first error rather than repeatedly recreating the container and losing the useful context.

Understand all three ports and the writable data volume

The official Compose file publishes three TCP ports and mounts a named volume at /data. They are not interchangeable: the browser loads the dashboard from 8080, the dashboard talks directly to Mihomo on 9090, and applications send HTTP or SOCKS proxy traffic to 7890.

Host portRoleTypical check
8080Dashboard UI and control-agent APIOpen http://<host>:8080
9090Mihomo Clash API and WebSocketUse http://<host>:9090 as the UI endpoint
7890Mihomo mixed HTTP/SOCKS proxyPoint an authorized client at <host>:7890

The named volume metacubexd-data is mounted at /data. It holds profiles, the active configuration, geo data, kernel-related downloads, and runtime caches. The directory must remain writable; making it read-only can cause the bundled kernel to exit with a non-zero status.

Publishing a Docker port does not override Mihomo's listener policy. Mihomo defaults allow-lan to false; with bridge networking, the mixed listener then remains on the container loopback, so 7890:7890 alone does not make the proxy reachable from the host or LAN. To use the published proxy port, set allow-lan: true in the active profile, constrain bind-address and lan-allowed-ips to the intended networks, and combine this with authentication, a narrow Docker host binding, and the host firewall. Recheck these values after changing profiles.

If only the host side changes, for example to 18080:8080, the container still listens on 8080 but the browser and host-side health request must use 18080. The same rule applies to host-side changes for 9090 and 7890: use the new host port in the dashboard endpoint or client. If CONTROL_PORT itself changes, update both the mapping's right side and the healthcheck URL; keep other internal listener variables paired with their mapping targets as well.

A running container is not an empty appliance: recreating the service keeps the named volume, but deleting that volume removes its persistent state. Include the volume in your backup plan before changing images or profiles.

Open the dashboard and connect it to the bundled Mihomo API

Open http://<host>:8080 in a browser. The server injects CONTROL_TOKEN into its same-origin page so the dashboard can probe /api/control; there is no separate field where you should paste that token. This convenience does not authenticate people who can reach the dashboard, which is why network restrictions remain necessary.

For the Mihomo endpoint, enter http://<host>:9090 as the URL and enter exactly the value stored in CLASH_SECRET as the Secret. Port 8080 is the web and agent surface, not the Clash API; pointing the endpoint at 8080 will not connect the dashboard to Mihomo.

Because ports 8080 and 9090 are different browser origins, the active Mihomo profile may need external-controller-cors to allow the exact dashboard origin, such as http://192.168.1.20:8080. If a public page accesses a private-network controller, also assess browser Private Network Access (PNA) behavior and enable allow-private-network: true only where that access is intended. If the page loads but backend requests fail, inspect the browser console for CORS or PNA errors before changing the secret or rebuilding the container.

An HTTPS dashboard also has a Mixed Content boundary. A page loaded over HTTPS is normally blocked from calling an insecure http://<host>:9090 HTTP/WebSocket endpoint before CORS is evaluated. Put port 9090 behind an HTTPS/WSS reverse proxy, or configure Mihomo's external-controller-tls with a valid certificate, and enter the matching https:// endpoint. Resolve TLS and Mixed Content first, then address CORS and PNA.

Verify one layer at a time: first load the page on 8080, then confirm the agent area is available, then connect the 9090 endpoint with CLASH_SECRET, and only after that test the mixed proxy on 7890.

MetaCubeXD desktop overview dashboard from the official project
Overview from the official MetaCubeXD repository. Its demo endpoint and traffic data are not evidence about your deployment.

Treat the health endpoint as a UI and agent check only

The server health endpoint is GET /api/control/health on CONTROL_PORT, which is 8080 in the official Compose file. A direct local check is:

Shellcurl -i http://127.0.0.1:8080/api/control/health

If the mapping's host side was changed to 18080:8080, run this host-side request against 18080. If CONTROL_PORT was changed inside the container, update the mapping target and the Compose healthcheck URL at the same time.

An HTTP 200 response means the dashboard server and control agent are responding. It does not assert that the bundled Mihomo process is running, that a profile passed validation, that the endpoint Secret is correct, or that outbound proxy traffic works. The Compose health status has the same limited meaning because it calls this endpoint.

  1. Use docker compose ps to confirm container and health state.
  2. Open the kernel panel and confirm that the bundled kernel reports running.
  3. Check docker compose logs --tail=100 metacubexd for profile or kernel errors.
  4. Confirm the dashboard connects to http://<host>:9090 with CLASH_SECRET.
  5. Test one authorized client through the mixed port and inspect the connection or rule log.

This sequence prevents a common false positive: declaring deployment complete because port 8080 returns 200 while the kernel has exited due to a read-only /data, invalid YAML, a conflicting port, or another runtime error.

Restrict published ports before leaving a trusted LAN

Mappings such as 8080:8080, 9090:9090, and 7890:7890 publish on every host interface by default. On a VPS or a host with public routing, that can expose the dashboard, the Clash API, and the proxy port far beyond the machine itself. A strong CONTROL_TOKEN does not fix dashboard exposure because the browser receives that token.

For local-only administration, bind the host side to loopback, for example 127.0.0.1:8080:8080, and reach it through a controlled local path. For a trusted LAN, restrict source networks with the host firewall or another ACL. If remote browser access is required, put the UI behind an authenticated TLS reverse proxy and separately protect port 9090 and port 7890; do not assume the UI proxy automatically covers them.

CLASH_SECRET protects the external controller, while Mihomo's authentication setting can protect HTTP, SOCKS, and mixed proxy users. These controls complement, rather than replace, network filtering. Avoid exposing an unauthenticated mixed proxy or a broadly allowed controller to the public internet.

Review the effective exposure: check the host firewall, cloud security group, reverse-proxy routes, and Compose port bindings together. Testing only from the host does not show what an outside network can reach.

Update without deleting the volume, and keep a rollback reference

The official example uses ghcr.io/metacubex/metacubexd-server:latest. That tag is intentionally moving: the same Compose file may pull a different image later. Before updating, record the currently running image identity, back up the state stored in metacubexd-data, and keep the previously tested image digest or another recoverable reference.

Shelldocker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 metacubexd

After the update, repeat the layered checks: UI and agent health, kernel running state, endpoint connection, profile availability, and one proxy request. If the new image fails, restore the backed-up data when necessary, point the Compose service back to the recorded working image reference, and run docker compose up -d again.

Do not use docker compose down -v as a routine update or troubleshooting command. The -v option removes named volumes, including the persistent metacubexd-data volume this deployment relies on. Recreating a container and deleting its data are different operations.

Reproducibility choice: latest follows upstream automatically; an inspected immutable digest favors repeatable rollback. Whichever policy you use, document it instead of assuming that “latest” identifies one permanent build.

Keep TUN as an advanced Linux boundary, not the default deployment

The official Compose file starts in proxy-only mode and works without TUN. That is the appropriate baseline for serving the dashboard, managing profiles, and offering the mixed proxy to explicitly configured applications. Test this baseline before adding privileged networking.

The official Linux TUN override requires network_mode: host, the NET_ADMIN capability, and the device mapping /dev/net/tun:/dev/net/tun. The active Mihomo profile must also contain an appropriate tun: configuration. With host networking enabled, Docker ignores the Compose ports: mappings; ports 8080, 9090, and 7890 bind directly through the host network namespace.

Proxy-only baselineAdvanced TUN override
Uses published ports:Uses host networking; published mappings are ignored
No NET_ADMIN or TUN deviceRequires NET_ADMIN and /dev/net/tun
Applications explicitly use port 7890The active profile defines network-layer TUN behavior

These permissions expand the container's impact on host networking. They do not, by themselves, configure forwarding, NAT, policy routing, DNS policy, or firewall rules for other LAN devices. This article therefore stops at the documented TUN prerequisites and does not present the container as a ready-made network gateway.

Official sources and version scope

Version numbers, filenames, and interface labels can change. Confirm them in the linked project documentation before making changes.