Run Attu with local Milvus mutual TLS in Docker
May 13, 2026 · View on GitHub
This guide shows how to run Attu against a local Milvus standalone deployment with mutual TLS (mTLS) enabled. It is intended for local development and verification.
Tested with:
milvusdb/milvus:v3.0-betazilliz/attu:v3.0.0-beta.4- A Docker Compose Milvus deployment using the Docker network named
milvus
Do not reuse these self-signed certificate commands for production as-is. For production, use certificates issued and rotated by your organization's CA or secret-management system.
1. Generate local TLS certificates
The commands below create a local CA, a Milvus server certificate, and an Attu client certificate under ~/workspace/volumes/milvus/tls.
The server certificate includes standalone in the Subject Alternative Name (SAN), because the Attu container connects to Milvus through Docker DNS at standalone:19530 in the examples below.
CERT_DIR="$HOME/workspace/volumes/milvus/tls"
mkdir -p "$CERT_DIR"
cat > "$CERT_DIR/openssl.cnf" <<'EOF'
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req
[ dn ]
C = US
ST = CA
L = Local
O = Milvus Local Test
OU = Attu TLS Test
CN = milvus-standalone
[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
[ v3_req ]
basicConstraints = CA:false
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = milvus-standalone
DNS.2 = standalone
DNS.3 = localhost
DNS.4 = milvus
DNS.5 = host.docker.internal
IP.1 = 127.0.0.1
IP.2 = ::1
[ v3_client ]
basicConstraints = CA:false
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
EOF
openssl genrsa -out "$CERT_DIR/ca.key" 4096
openssl req -x509 -new -nodes -key "$CERT_DIR/ca.key" -sha256 -days 3650 \
-subj "/C=US/ST=CA/L=Local/O=Milvus Local Test/OU=Attu TLS Test/CN=Milvus Local Test CA" \
-out "$CERT_DIR/ca.pem" \
-extensions v3_ca -config "$CERT_DIR/openssl.cnf"
openssl genrsa -out "$CERT_DIR/server.key" 2048
openssl req -new -key "$CERT_DIR/server.key" -out "$CERT_DIR/server.csr" \
-subj "/C=US/ST=CA/L=Local/O=Milvus Local Test/OU=Attu TLS Test/CN=milvus-standalone" \
-config "$CERT_DIR/openssl.cnf"
openssl x509 -req -in "$CERT_DIR/server.csr" -CA "$CERT_DIR/ca.pem" -CAkey "$CERT_DIR/ca.key" -CAcreateserial \
-out "$CERT_DIR/server.pem" -days 3650 -sha256 -extensions v3_req -extfile "$CERT_DIR/openssl.cnf"
openssl genrsa -out "$CERT_DIR/client.key" 2048
openssl req -new -key "$CERT_DIR/client.key" -out "$CERT_DIR/client.csr" \
-subj "/C=US/ST=CA/L=Local/O=Milvus Local Test/OU=Attu TLS Test/CN=attu-client" \
-config "$CERT_DIR/openssl.cnf"
openssl x509 -req -in "$CERT_DIR/client.csr" -CA "$CERT_DIR/ca.pem" -CAkey "$CERT_DIR/ca.key" -CAcreateserial \
-out "$CERT_DIR/client.pem" -days 3650 -sha256 -extensions v3_client -extfile "$CERT_DIR/openssl.cnf"
chmod 755 "$CERT_DIR"
chmod 644 "$CERT_DIR"/*.pem "$CERT_DIR"/*.key
ls -l "$CERT_DIR"
Expected files:
ca.pem— CA certificate used by Attu to verify Milvus.server.pem/server.key— Milvus server certificate and private key.client.pem/client.key— Attu client certificate and private key for mTLS.
2. Enable mTLS in Milvus Docker Compose
In the Milvus docker-compose.yml, configure the standalone service to enable two-way TLS and point Milvus at the certificate files mounted under /var/lib/milvus/tls.
Example standalone service fragment:
services:
standalone:
container_name: milvus-standalone
image: milvusdb/milvus:v3.0-beta
command: ["milvus", "run", "standalone"]
security_opt:
- seccomp:unconfined
environment:
ETCD_ENDPOINTS: etcd:2379
MINIO_ADDRESS: minio:9000
MQ_TYPE: woodpecker
COMMON_SECURITY_TLSMODE: 2
PROXY_HTTP_ENABLED: "false"
TLS_SERVERPEMPATH: /var/lib/milvus/tls/server.pem
TLS_SERVERKEYPATH: /var/lib/milvus/tls/server.key
TLS_CAPEMPATH: /var/lib/milvus/tls/ca.pem
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus
ports:
- "19530:19530"
- "9091:9091"
depends_on:
- etcd
- minio
networks:
default:
name: milvus
Notes:
COMMON_SECURITY_TLSMODE: 2enables Milvus external gRPC mutual TLS.TLS_SERVERPEMPATH,TLS_SERVERKEYPATH, andTLS_CAPEMPATHconfigure the server cert/key and CA.PROXY_HTTP_ENABLED: "false"avoids Milvus trying to share the same port for proxy HTTP and external gRPC when TLS mode is enabled.- The Compose network name is
milvus; Attu joins this network in thedocker runcommand below.
Restart Milvus:
docker compose -f ~/workspace/docker-compose.yml up -d
docker compose -f ~/workspace/docker-compose.yml ps
Wait until milvus-standalone is healthy.
3. Pull the Attu image
docker pull zilliz/attu:v3.0.0-beta.4
4. Start Attu with the mTLS connection pre-configured
This command starts Attu, mounts the generated certificates read-only, and creates a default Milvus connection on first launch.
docker rm -f attu-tls-test >/dev/null 2>&1 || true && \
docker run -d \
--name attu-tls-test \
--network milvus \
-p 3000:3000 \
-v "$HOME/workspace/volumes/milvus/tls:/etc/attu/certs:ro" \
-e ATTU_DB_PATH=/tmp/attu-tls-test.db \
-e MILVUS_NAME=milvus-mtls-local \
-e MILVUS_ADDRESS=standalone:19530 \
-e MILVUS_SSL=true \
-e MILVUS_TLS_ROOT_CERT_PATH=/etc/attu/certs/ca.pem \
-e MILVUS_TLS_PRIVATE_KEY_PATH=/etc/attu/certs/client.key \
-e MILVUS_TLS_CERT_CHAIN_PATH=/etc/attu/certs/client.pem \
-e MILVUS_TLS_SERVER_NAME=standalone \
zilliz/attu:v3.0.0-beta.4
Open Attu at http://localhost:3000.
For a persistent Attu database, replace the temporary DB path with a named volume:
-v attu-data:/data \
-e ATTU_DB_PATH=/data/attu.db \
The pre-configured connection is inserted only once per Attu database. If you change TLS environment variables and reuse the same ATTU_DB_PATH, edit the connection in the UI or start with a fresh Attu database.
5. Optional command-line verification
After attu-tls-test starts, you can verify the exact same mTLS settings from inside the Attu container with the Milvus Node.js SDK bundled in the image:
docker exec attu-tls-test sh -lc 'cd /app/.output/server && node -e "import(\"@zilliz/milvus2-sdk-node\").then(async ({ MilvusClient }) => { const client = new MilvusClient({ address: \"standalone:19530\", ssl: true, tls: { rootCertPath: \"/etc/attu/certs/ca.pem\", privateKeyPath: \"/etc/attu/certs/client.key\", certChainPath: \"/etc/attu/certs/client.pem\", serverName: \"standalone\" } }); const result = await client.listCollections(); console.log(JSON.stringify(result, null, 2)); await client.closeConnection(); }).catch((error) => { console.error(error); process.exit(1); })"'
A successful response includes status.error_code: "Success" and the current collection list.
Troubleshooting
proxy server(http) and external grpc server share the same port, tls mode must be 0
Set PROXY_HTTP_ENABLED: "false" in the Milvus standalone service, or configure proxy HTTP to use a different port.
Hostname verification fails
Make sure the value of MILVUS_TLS_SERVER_NAME matches a DNS name in the server certificate SAN list. In this guide, Attu connects to standalone:19530, so MILVUS_TLS_SERVER_NAME=standalone is used.
mTLS handshake fails
Check that:
- Milvus is running with
COMMON_SECURITY_TLSMODE: 2. - Attu has all three mTLS paths set:
MILVUS_TLS_ROOT_CERT_PATHMILVUS_TLS_PRIVATE_KEY_PATHMILVUS_TLS_CERT_CHAIN_PATH
- The Attu certificate mount is readable inside the container:
docker exec attu-tls-test ls -l /etc/attu/certs
Attu starts but the default connection does not change
The default connection created from MILVUS_ADDRESS is inserted only once per Attu SQLite database. Remove the test container and use a fresh ATTU_DB_PATH, or edit the saved connection in the UI.