Recently inside a Discord server for Pangolin I had the most interesting discussion with somebody. I was seeking help as my service for vaultwarden had gone down... again. So I went seeking help. When I shared my docker-compose.yml file at the time it had made the other’s curious about why I approached my network in this way. Here is the docker-compose.yml for you to make fun of.
name: vaultwarden
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
hostname: vaultwarden
restart: unless-stopped
environment:
DOMAIN: https://vault.whoisjason.me
volumes:
- /media/jason/Storage/BitWarden:/data
labels:
icon: https://icon.casaos.io/main/all/vaultwarden.png
networks:
- vaultwarden
vaultwarden-newt:
image: fosrl/newt
container_name: vaultwarden-newt
restart: unless-stopped
environment:
- PANGOLIN_ENDPOINT=https://pangolin.whoisjason.me
- NEWT_ID=palqb5rnuzkk49h
- NEWT_SECRET=mvjn9yscn0dja26bvhzojxk4pb9svbqgpmnnrhoh0peacr7v
networks:
- vaultwarden
networks:
vaultwarden:
name: vaultwardenNow before anything, that newt has been deleted to sharing those ‘keys’ is fine here. Secondly, do you notice how there is a whole newt attached here, totally unnecessary. But what did I gain from that? Well the docker container was not exposed on my LAN, as vaultwarden is very picky about HTTPS and does not allow insecure HTTP traffic. So there was no point in leaving the http open on my LAN. This closed it down, and doubled the amount of newts I actually needed in my setup.
Why not?
Well, it doesn’t really hurt anything, but I want my network to be as efficient as possible, and it’s really cool when you can use hostnames, and even cooler when you can use hostnames and keep your network more secure.
But then I got to wondering, why have 2 newts, when a single docker network is all I really need. a docker network acts like a router, assigning IP addresses, and sharing a central point of communication, and fundamentally that is what a router is, and no I’m not talking about Docker’s IPVLAN networks, but those are cool too.
What do you do instead?
The solution was painful, and locked me out of my server a few times in the process as I was testing Portainer under a domain, a very bad idea.
The solution, was to modify the pre-existing newt to this:
services:
newt:
image: fosrl/newt
container_name: working-newt-home
restart: unless-stopped
environment:
- PANGOLIN_ENDPOINT=https://pangolin.whoisjason.me
- NEWT_ID=LMAOJKJK
- NEWT_SECRET=IMACTUALLYHIDINGTHISONE
networks:
- newtnet
networks:
newtnet:
name: newtnet
driver: bridgeNow what is cool about this is, the ‘newtnet’ which allows me to connect other docker containers I have running, to newtnet.
Then I can go over to a service like vaultwarden, where I
1. Want to use the hostname
2. Don’t want it on the LAN
and I modify the compose to look like this
name: vaultwarden
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
hostname: vaultwarden
restart: unless-stopped
environment:
DOMAIN: https://vault.whoisjason.me
volumes:
- /media/jason/Storage/BitWarden:/data
networks:
- vaultnet
networks:
vaultnet:
name: newtnet
external: trueCheck out here how we can call it whatever we want, like vaultnet, but within the compose we say that vaultnet=newtnet
Why is this better?
Because!!! if I have another service like emby, you can notice here that nothing is special about it at all, it connects straight onto the LAN, so I can watch my movies without internet. All while still using that same newt, using ‘vaultwarden:80’ and not exposing it to the LAN. All without opening up a second wireguard tunnel connection.
services:
emby:
image: emby/embyserver:latest
container_name: emby
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=US/Arizona
ports:
- 8096:8096
- 8920:8920
devices:
- /dev/dri:/dev/dri
volumes:
- /DATA/AppData/emby/config:/config
- /media/jason/14TB-Beast/TV:/media/jason/14TB-Beast/TV
- /media/jason/14TB-Beast/Movies:/media/jason/14TB-Beast/MoviesBut wait there’s more
So then from there, what if you wanted to add another service, a service that again, doesn’t need to touch the LAN, but this time a trickier, more sinister service, that I had to write my own documentation for Pangolin for! 😡
Rybbit example
services:
clickhouse:
container_name: clickhouse
image: clickhouse/clickhouse-server:25.4.2
volumes:
- clickhouse-data:/var/lib/clickhouse
configs:
- source: clickhouse_network
target: /etc/clickhouse-server/config.d/network.xml
- source: clickhouse_json
target: /etc/clickhouse-server/config.d/enable_json.xml
- source: clickhouse_logging
target: /etc/clickhouse-server/config.d/logging_rules.xml
- source: clickhouse_user_logging
target: /etc/clickhouse-server/config.d/user_logging.xml
environment:
- CLICKHOUSE_DB=${CLICKHOUSE_DB:-analytics}
- CLICKHOUSE_USER=${CLICKHOUSE_USER:-default}
- CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD:-frog}
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8123/ping"]
interval: 3s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
postgres:
image: postgres:17.4
container_name: postgres
environment:
- POSTGRES_USER=${POSTGRES_USER:-frog}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-frog}
- POSTGRES_DB=${POSTGRES_DB:-analytics}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 3s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
backend:
image: ghcr.io/rybbit-io/rybbit-backend:${IMAGE_TAG:-latest}
container_name: backend
environment:
- NODE_ENV=production
- CLICKHOUSE_HOST=http://clickhouse:8123
- CLICKHOUSE_DB=${CLICKHOUSE_DB:-analytics}
- CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD:-frog}
- POSTGRES_HOST=postgres
- POSTGRES_PORT=5432
- POSTGRES_DB=${POSTGRES_DB:-analytics}
- POSTGRES_USER=${POSTGRES_USER:-frog}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-frog}
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
- BASE_URL=${BASE_URL}
- DISABLE_SIGNUP=${DISABLE_SIGNUP}
- DISABLE_TELEMETRY=${DISABLE_TELEMETRY}
- MAPBOX_TOKEN=${MAPBOX_TOKEN}
depends_on:
clickhouse:
condition: service_healthy
postgres:
condition: service_started
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3001/api/health"]
interval: 3s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
networks:
- default
- newtnet
client:
image: ghcr.io/rybbit-io/rybbit-client:${IMAGE_TAG:-latest}
container_name: client
environment:
- NODE_ENV=production
- NEXT_PUBLIC_BACKEND_URL=${BASE_URL}
- NEXT_PUBLIC_DISABLE_SIGNUP=${DISABLE_SIGNUP}
depends_on:
- backend
restart: unless-stopped
networks:
- default
- newtnet
volumes:
clickhouse-data:
postgres-data:
configs:
clickhouse_network:
content: |
<clickhouse>
<listen_host>0.0.0.0</listen_host>
</clickhouse>
clickhouse_json:
content: |
<clickhouse>
<settings>
<enable_json_type>1</enable_json_type>
</settings>
</clickhouse>
clickhouse_logging:
content: |
<clickhouse>
<logger>
<level>warning</level>
<console>true</console>
</logger>
<query_thread_log remove="remove"/>
<query_log remove="remove"/>
<text_log remove="remove"/>
<trace_log remove="remove"/>
<metric_log remove="remove"/>
<asynchronous_metric_log remove="remove"/>
<session_log remove="remove"/>
<part_log remove="remove"/>
<latency_log remove="remove"/>
<processors_profile_log remove="remove"/>
</clickhouse>
clickhouse_user_logging:
content: |
<clickhouse>
<profiles>
<default>
<log_queries>0</log_queries>
<log_query_threads>0</log_query_threads>
<log_processors_profiles>0</log_processors_profiles>
</default>
</profiles>
</clickhouse>
networks:
newtnet:
name: newtnet
external: trueNow look at that, 2 networks! meaning that postgres never touches the internet, and Rybbit works flawlessly. and my Pangolin configuration can look like this
Wrapping up
So I found this all kind of cool and a way for me to learn more about docker networking and become more familiar with my own network. I came from using Cloudflare tunnels where it look me way too long to realize that I could re-use a single tunnel if it was on a different port. Which has led me here today with a greater knowledge of how docker and networking all works. Docker networks can be confusing to manage and a lot to wrap your head around at a first, but with enough trail and error and crashing services you are trying to desperately to rely on will get you far.


