Hey, just wondering where the latest guides would be for setting up via docker compose.
I've not messed with ziti for a while, well over a year so my config will probably be outdated so I wanted to set it all up from scratch.
I think I have a controller up and running via docker compose but now need to get a router up at the same node (a cloud hosted vm)
I have the controller running with
[root]# cat compose.yml
volumes:
ziti-controller:
driver: local
networks:
ziti:
driver: bridge
services:
chown-controller:
image: busybox
command: chown -R ${ZIGGY_UID:-2171} /ziti-controller
volumes:
- ziti-controller:/ziti-controller
ziti-controller:
image: ${ZITI_CONTROLLER_IMAGE:-openziti/ziti-controller}
depends_on:
chown-controller:
condition: service_completed_successfully
user: ${ZIGGY_UID:-2171}
volumes:
- ziti-controller:/ziti-controller
networks:
ziti:
aliases:
- ${ZITI_CTRL_ADVERTISED_ADDRESS:-ziti-controller}
# assign override vars in an .env file or export from parent env to ensure consistency throughout the compose
# project
environment:
# *** these are the important vars to set to bootstrap the configuration during first run***
ZITI_CTRL_ADVERTISED_ADDRESS: ${ZITI_CTRL_ADVERTISED_ADDRESS:-ziti-controller} # FQDN of the controller
ZITI_CTRL_ADVERTISED_PORT: ${ZITI_CTRL_ADVERTISED_PORT:-1280} # TCP port of the controller
ZITI_PWD: ${ZITI_PWD:-} # password for the default admin user
# *** less relevant vars below ***
ZITI_BOOTSTRAP: true # bootstrap the controller if "true"
ZITI_BOOTSTRAP_PKI: true # make the default PKI if "true"; requires ZITI_BOOTSTRAP=true
ZITI_BOOTSTRAP_CONFIG: true # make config file from env vars and defaults if "true," overwrite if "force"; requires ZITI_BOOTSTRAP=true
ZITI_BOOTSTRAP_DATABASE: true # make the default admin user if "true"; requires ZITI_BOOTSTRAP=true
ZITI_AUTO_RENEW_CERTS: true # renew certs automatically every startup; requires ZITI_BOOTSTRAP_PKI=true
ZITI_BOOTSTRAP_CONFIG_ARGS: # additional arguments to: ziti create config controller
command: run config.yml
ports:
# ensure this port matches the value of ZITI_CTRL_PORT in the container
- ${ZITI_INTERFACE:-0.0.0.0}:${ZITI_CTRL_ADVERTISED_PORT:-1280}:${ZITI_CTRL_ADVERTISED_PORT:-1280}
expose:
- ${ZITI_CTRL_ADVERTISED_PORT:-1280}
restart: unless-stopped
healthcheck:
test:
- CMD
- ziti
- agent
- stats
interval: 3s
timeout: 3s
retries: 5
start_period: 15s
and env
ZITI_CTRL_ADVERTISED_ADDRESS=ziti-controller.wizznet.co.uk
ZITI_CTRL_ADVERTISED_ADDRESS=ziti-controller.wizznet.co.uk
ZITI_CTRL_EDGE_ADVERTISED_ADDRESS=ziti-controller.wizznet.co.uk
ZITI_CTRL_EDGE_IP_OVERRIDE=10.60.0.120
ZITI_BOOTSTRAP=true
ZITI_BOOTSTRAP_PKI=true
ZITI_USER=admin
ZITI_PWD=password
ZITI_BOOTSTRAP_CONFIG=true
ZITI_BOOTSTRAP_DATABASE=true
ZITI_AUTO_RENEW_CERTS=true
I then tried to create a router but struggling.
I first did this to create a jwt (after logging into controller)
ziti edge create edge-router contabo-router --jwt-output-file contabo-router.jwt
then a config
ziti create config router edge --routerName contabo-router
Now its at this point where I'm a bit unsure of how to use all this in another docker compose for the router, I've tried a few combinations using snippets of what I'm finding online and can't seem to get anything to work.
Any ideas of how to proceed with the router?
Thanks!
Jon.
Hi again @bodleytunes, the latest doc for docker is under deployments here Deploying with Docker | OpenZiti
Were you able to find the compose file referenced? ziti/dist/docker-images/ziti-router/compose.yml at main ยท openziti/ziti ยท GitHub
I don't know if there's one compose file that ties both together and I think @qrkourier will be out for a few days but he should be back next week. He's more familiar with these and will probably be able to help better.
1 Like
Thanks I'll take a look!
Jon.
Hi,
I tried that latest compose file and used my generated jwt token for ZITI_ENROL_TOKEN in the .env but it doesn't seem to like it and complains.
And I generated the token for router on the controller with
ziti edge create edge-router contabo-router2 --jwt-output-file contabo-router.jwt
Whoops typo! ZITI_ENROL_TOKEN 
Getting further now:
","msg":"tproxy config: udpIdleTimeout = [5m0s]","time":"2025-05-19T10:28:17.393Z"}
ziti-router-1 | {"file":"github.com/openziti/ziti/tunnel/intercept/tproxy/tproxy_linux.go:103","func":"github.com/openziti/ziti/tunnel/intercept/tproxy.New","level":"info","msg":"tproxy config: udpCheckInterval = [30s]","time":"2025-05-19T10:28:17.393Z"}
ziti-router-1 | {"error":"netlink receive: operation not permitted","file":"github.com/openziti/ziti/tunnel/intercept/tproxy/tproxy_linux.go:108","func":"github.com/openziti/ziti/tunnel/intercept/tproxy.New","level":"error","msg":"unable to add 100.64.0.1/10 to lo","time":"2025-05-19T10:28:17.394Z"}
ziti-router-1 | {"error":"failed to initialize tproxy interceptor: netlink receive: operation not permitted","file":"github.com/openziti/ziti/router/xgress_edge_tunnel/factory_wrapper.go:159","func":"github.com/openziti/ziti/router/xgress_edge_tunnel.NewFactoryWrapper.func2","level":"fatal","msg":"error starting","time":"2025-05-19T10:28:17.394Z"}
ziti-router-1 exited with code 0
Also added these to the router container thinking it might be network related permissions
cap_add:
- NET_ADMIN
- NET_RAW
- SYS_ADMIN
security_opt:
- apparmor=unconfined
managed to get it to run now but had to disable tproxy mode and change it to host in the config file in the docker volume.
I notice my router is showing as online but not as public. How can I change this to public without re-enrolling and starting everything from scratch?
Seems I'm getting that tproxy error even on my local new tunneller/router which is running in an lxc container (in docker again). I might try a privileged lxc container next to see if that makes any difference.
However, the contabo server I tried on earlier above is a VM running in the cloud so still really baffled as to how to get tproxy working on that.
I think I need tproxy at the local end because im looking to intercept some udp traffic for a docker service and send it down the tunnel to a remote headend at the contabo server.
I'm going to be using the sidecar trick with the network_mode:ziti-tunnel
Got my local router up now, just need to sort this tproxy stuff?

ziti edge list edge-routers
โญโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโฌโโโโโโโโโโโโโโโโฌโโโโโโโฌโโโโโโโโโโโโโฎ
โ ID โ NAME โ ONLINE โ ALLOW TRANSIT โ COST โ ATTRIBUTES โ
โโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโโโโโโโค
โ Yk0KxGqfw โ contabo-router2 โ true โ true โ 0 โ โ
โ lDOk2cqTY โ local-router-lsk15 โ true โ true โ 0 โ โ
โฐโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโดโโโโโโโโโโโโโฏ
Hi @bodleytunes,
Looks like you got it working without my help! That's great. You're now looking to intercept some traffic within docker so that you can use the router to intercept traffic. I assume you have other containers that you're looking to have access to the overlay, right?
Yes you'll need to figure out the tproxy stuff and you'll need to figure out how to declare a container as another container's network. If you're interested this video https://www.youtube.com/watch?v=odMy4F_iPdU or this discourse post Add new service on a separate "green" network in docker compose quickstart have similar information and yes they demonstrate using network_mode
.
I think you're on the right track!
1 Like
One more thing, I was trying to create a Zac console, manually, and I don't think I'm using the right certs for it.
I created an admin identity then did ops unwrap on the json file and used the generated .key and .cert files in the env var for Zac console but it doesn't start it says it still looking for certificates so I don't think its the right type of cert?
[root]# docker compose up
[+] Running 1/1
โ Container openziti-web-console-ziti-console-1 Created 0.0s
Attaching to ziti-console-1
ziti-console-1 | waiting for server key to exist...
ziti-console-1 | waiting for server key to exist...
Do I need to generate intermediate ones or something? If so what would the command be for that?
Cheers,
Jon.
OK those certs did work in the end just had to copy them to the correct location so they were mounted to the container
ziti-console:
- ZAC_SERVER_CERT_CHAIN=/persistent/pki/certs/jon.cert
- ZAC_SERVER_KEY=/persistent/pki/keys/jon.key
full compose
[root]# cat compose.yml
services:
ziti-console:
image: openziti/zac
working_dir: /usr/src/app
environment:
#- ZAC_SERVER_CERT_CHAIN=/persistent/pki/${ZITI_CTRL_EDGE_ADVERTISED_ADDRESS:-ziti-edge-controller}-intermediate/certs/${ZITI_CTRL_EDGE_ADVERTISED_ADDRESS:-ziti-edge-controller}-server.cert
#- ZAC_SERVER_KEY=/persistent/pki/${ZITI_CTRL_EDGE_ADVERTISED_ADDRESS:-ziti-edge-controller}-intermediate/keys/${ZITI_CTRL_EDGE_ADVERTISED_ADDRESS:-ziti-edge-controller}-server.key
- ZAC_SERVER_CERT_CHAIN=/persistent/pki/certs/jon.cert
- ZAC_SERVER_KEY=/persistent/pki/keys/jon.key
- ZITI_CTRL_EDGE_ADVERTISED_ADDRESS=${ZITI_CTRL_EDGE_ADVERTISED_ADDRESS:-ziti-edge-controller}
- ZITI_CTRL_EDGE_ADVERTISED_PORT=${ZITI_CTRL_EDGE_ADVERTISED_PORT:-1280}
- ZITI_CTRL_NAME=${ZITI_CTRL_NAME:-ziti-edge-controller}
- ZAC_CONTROLLER_URLS=${ZAC_CONTROLLER_URLS:-ziti-edge-controller:1280}
- PORTTLS=8443
ports:
- ${ZITI_INTERFACE:-0.0.0.0}:8443:8443
volumes:
- ./ziti-fs:/persistent
Hey it's great you got it working! That's not exactly the way i would have generated those certs, myself. I would have suggested you use the ziti pki command along with the root ca from the controller container.
i also would recommend you deliver the ZAC from the controller, on the same API as the management API (then there's no new certs to generate). Is there any reason you wanted a separate container? Maybe you have a good reason for wanting another container, instead of delivering it from the controller? Or perhaps it wasn't clear that you could do that?
1 Like
I'm not sure really I just wanted to spin things up separately so I could understand a bit more what's going on in the background.
Just need to sort the tproxy stuff now, if I fix it will report back.
OK update, I noticed that the relavent kernel modules were not even loaded on my proxmox server, so I loaded them. I then made the lxc container privileged but still seem to have the same tproxy errors.
The mystery continues.
Think I'm getting somewhere.
Really strange results
Added a netshoot container as a service along with the router, made it share same network ns
then docker exec'd into netshoot and ran ip addr add 100.64.0.5/10 dev lo and it works and can see the ip address.
But, if docker exec'ing into the ziti router container if I try and run same type of commands I get operation not permitted.
[ziggy@9d958b0bde5a ~]$ ip addr add 100.64.0.10/10 dev lo
RTNETLINK answers: Operation not permitted
I'm getting this because its running it as "ziggy" and ziggy doesn't have permissions.
Would this also affect the router service? How to give it root permissions?