Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.allthingslinux.org/llms.txt

Use this file to discover all available pages before exploring further.

atl.chat uses a Tailscale overlay to connect production servers, a single Docker bridge network for inter-service communication, and structured DNS zones with SRV records for IRC and XMPP client discovery.

Network topology

Production runs on two Tailscale nodes within the CGNAT subnet 100.64.0.0/10:
NodeTailscale IPRole
atl.network (gateway)100.64.1.0Nginx Proxy Manager, TLS termination, public ingress
atl.chat (services)100.64.7.0All Dockerised chat services
The ATL_GATEWAY_IP and ATL_CHAT_IP variables in .env control which IP addresses services bind to. In development, .env.dev overrides ATL_CHAT_IP to 127.0.0.1 so ports bind to localhost only.

Traffic flow

HTTP/S traffic and raw TCP (IRC, XMPP) follow different paths through the gateway:
  1. HTTP/S trafficInternet → Cloudflare → atl.network (Nginx Proxy Manager) → Tailscale tunnel → atl.chat (containers)
  2. TCP traffic (IRC/XMPP)Internet → atl.network (NPM stream pass-through) → Tailscale tunnel → atl.chat (TCP ports)
All inter-node traffic is encrypted by Tailscale. TLS termination for HTTPS happens at the gateway; IRC and XMPP handle their own TLS within the containers.

Docker network model

All Dockerised services share a single bridge network named atl-chat, defined in infra/compose/networks.yaml. Every compose fragment includes this file and attaches its services to the same network.
  • Services discover each other by container name (e.g., the bridge connects to atl-irc-server).
  • Only services that need external access bind host ports.
  • There is no network segmentation between containers — all services can reach each other on the bridge network.
  • Atheme uses network_mode: service:atl-irc-server, sharing UnrealIRCd’s network namespace. It connects via 127.0.0.1:6901 (localhost within the shared stack) and its HTTP API (port 8081) is exposed through UnrealIRCd’s port bindings.
For production network hardening, see the Security page.

Port registry

The following table lists every port exposed by the Docker Compose stack, verified against infra/compose/*.yaml and compose.yaml. The “Bind IP” column shows which variable controls the host bind address.

External ports (host-mapped)

PortServiceContainerProtocolBind IPEnv VariableCompose File
6697UnrealIRCdatl-irc-serverIRC (TLS)ATL_CHAT_IPIRC_TLS_PORTirc.yaml
6900UnrealIRCdatl-irc-serverIRC (S2S link)ATL_CHAT_IPIRC_SERVER_PORTirc.yaml
8600UnrealIRCdatl-irc-serverJSON-RPC (HTTPS)ATL_CHAT_IPIRC_RPC_PORTirc.yaml
8000UnrealIRCdatl-irc-serverIRC WebSocketATL_CHAT_IPIRC_WEBSOCKET_PORTirc.yaml
8081Athemeatl-irc-server¹HTTP (JSON-RPC)ATL_CHAT_IPATHEME_HTTPD_PORTirc.yaml
8080WebPanelatl-irc-webpanelHTTPWEBPANEL_PORTirc.yaml
5222Prosodyatl-xmpp-serverXMPP C2S (STARTTLS)PROSODY_C2S_PORTxmpp.yaml
5223Prosodyatl-xmpp-serverXMPP C2S (Direct TLS)PROSODY_C2S_DIRECT_TLS_PORTxmpp.yaml
5269Prosodyatl-xmpp-serverXMPP S2S (STARTTLS)PROSODY_S2S_PORTxmpp.yaml
5270Prosodyatl-xmpp-serverXMPP S2S (Direct TLS)PROSODY_S2S_DIRECT_TLS_PORTxmpp.yaml
5280Prosodyatl-xmpp-serverHTTP (BOSH/WebSocket)PROSODY_HTTP_PORTxmpp.yaml
5281XMPP Nginxatl-xmpp-nginxHTTPS (BOSH/WebSocket)PROSODY_HTTPS_PORTxmpp.yaml
5000Prosodyatl-xmpp-serverProxy65 (file transfer)PROSODY_PROXY65_PORTxmpp.yaml
9000The Loungeatl-theloungeHTTP (Web IRC)THELOUNGE_PORTthelounge.yaml
8090ObsidianIRCatl-obsidianircHTTP (Web IRC)OBSIDIANIRC_PORTobsidianirc.yaml
8082DozzledozzleHTTP (log viewer)DOZZLE_PORTcompose.yaml
¹ Atheme shares UnrealIRCd’s network namespace (network_mode: service:atl-irc-server), so its port 8081 is exposed through the atl-irc-server container’s port bindings.
Note: Dozzle is only available under the dev profile (docker compose --profile dev up). It is not started in production.

Internal ports (container-to-container only)

These ports are used for inter-service communication on the atl-chat Docker network and are not mapped to the host:
PortServiceProtocolUsed By
6901UnrealIRCdIRC (S2S uplink)Atheme connects via 127.0.0.1:6901 (shared namespace)
5347ProsodyXMPP componentBridge connects as bridge.atl.chat component
8081Atheme HTTPJSON-RPCBridge and Portal query Atheme via atl-irc-server:8081
8600UnrealIRCd RPCJSON-RPCWebPanel queries UnrealIRCd via atl-irc-server:8600

Bind address behaviour

The ATL_CHAT_IP variable controls which host IP the IRC stack ports bind to:
EnvironmentATL_CHAT_IPEffect
Development (.env.dev)127.0.0.1Ports only accessible from localhost
Production (.env)100.64.7.0Ports bind to the Tailscale IP, accessible only within the Tailnet
XMPP, WebPanel, The Lounge, and Dozzle ports do not use ATL_CHAT_IP — they bind to 0.0.0.0 by default. In production, firewall rules (see below) restrict access.

Tailscale overlay

Production servers communicate via Tailscale, which provides a mesh VPN using the CGNAT subnet 100.64.0.0/10. This eliminates the need for public IPs between servers and provides automatic key rotation, NAT traversal, and MagicDNS.

Setup

  1. Install Tailscale on both the gateway (atl.network) and the services node (atl.chat):
    # Ubuntu/Debian
    curl -fsSL https://tailscale.com/install.sh | sh
    sudo tailscale up --advertise-tags=tag:server
    
  2. Assign stable IPs in the Tailscale admin console:
    • atl.network100.64.1.0
    • atl.chat100.64.7.0
  3. Set the IPs in .env:
    ATL_GATEWAY_IP=100.64.1.0
    ATL_CHAT_IP=100.64.7.0
    
  4. Run just init to regenerate configs, then just prod to start services bound to the Tailscale IPs.

Service discovery

Services on the atl.chat node discover each other by Docker container name on the atl-chat bridge network (e.g., atl-irc-server, atl-xmpp-server). The gateway node (atl.network) reaches the services node via its Tailscale IP 100.64.7.0. Tailscale also provides MagicDNS, so you can optionally use atl-chat.tailnet-name.ts.net for internal service discovery between nodes. However, the compose stack uses container names for intra-node communication and Tailscale IPs for inter-node communication.

ACLs

Restrict Tailscale ACLs so only the gateway node can reach the services node on the required ports. Example Tailscale ACL policy:
{
  "acls": [
    {
      "action": "accept",
      "src": ["tag:gateway"],
      "dst": ["tag:server:6697,8000,5222,5223,5269,5270,5280,5281,9000,8080"]
    }
  ],
  "tagOwners": {
    "tag:gateway": ["autogroup:admin"],
    "tag:server": ["autogroup:admin"]
  }
}

DNS zone layout

atl.chat uses structured DNS zones for each protocol. All public DNS records point to the gateway node (atl.network), which proxies traffic through the Tailscale tunnel to the services node.

A/AAAA records

HostnamePoints ToPurpose
atl.chatGateway public IPMain web interface, XMPP domain
irc.atl.chatGateway public IPIRC server (TLS, WebSocket, RPC)
xmpp.atl.chatGateway public IPXMPP BOSH/WebSocket HTTPS endpoint
chat.atl.chatGateway public IPThe Lounge web IRC client
turn.atl.networkGateway public IPTURN/STUN server for XMPP media

SRV records for XMPP

XMPP clients and servers use SRV records to discover connection endpoints. These records are required for federation and recommended for client auto-configuration:
; Client-to-server (C2S) — STARTTLS on port 5222
_xmpp-client._tcp.atl.chat.  3600  IN  SRV  5 0 5222  xmpp.atl.chat.

; Client-to-server (C2S) — Direct TLS on port 5223
_xmpps-client._tcp.atl.chat. 3600  IN  SRV  5 0 5223  xmpp.atl.chat.

; Server-to-server (S2S) — STARTTLS on port 5269
_xmpp-server._tcp.atl.chat.  3600  IN  SRV  5 0 5269  xmpp.atl.chat.

; Server-to-server (S2S) — Direct TLS on port 5270
_xmpps-server._tcp.atl.chat. 3600  IN  SRV  5 0 5270  xmpp.atl.chat.
Note: The _xmpps-* records (with the s) advertise Direct TLS ports (5223/5270), which skip the STARTTLS negotiation step. Both STARTTLS and Direct TLS records should be published for maximum client compatibility.

SRV records for IRC

IRC does not have a standardised SRV record scheme, but you can optionally publish one for clients that support it:
; IRC client (TLS on port 6697)
_ircs._tcp.atl.chat.  3600  IN  SRV  5 0 6697  irc.atl.chat.

MUC and component subdomains

XMPP multi-user chat (MUC) and bridge components use subdomains of the XMPP domain. These do not need separate DNS records — Prosody handles them internally — but they must be resolvable if federation is enabled:
SubdomainPurpose
muc.atl.chatMUC (multi-user chat) rooms
bridge.atl.chatBridge XMPP component JID
upload.atl.chatHTTP file upload (mod_http_file_share)
pubsub.atl.chatPubSub service
proxy.atl.chatProxy65 file transfer

Firewall rules

Gateway node (atl.network)

The gateway is the only node with a public IP. Open these ports to the internet:
PortProtocolService
80TCPHTTP (Let’s Encrypt challenges, redirect to HTTPS)
443TCPHTTPS (Nginx Proxy Manager → web, BOSH, WebSocket)
6697TCPIRC TLS (stream pass-through to atl.chat)
5222TCPXMPP C2S STARTTLS (stream pass-through)
5223TCPXMPP C2S Direct TLS (stream pass-through)
5269TCPXMPP S2S STARTTLS (stream pass-through)
5270TCPXMPP S2S Direct TLS (stream pass-through)
3478UDPTURN (media relay)
5349TCPTURNS (TLS media relay)
Example ufw rules:
# Web
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# IRC
sudo ufw allow 6697/tcp

# XMPP
sudo ufw allow 5222/tcp
sudo ufw allow 5223/tcp
sudo ufw allow 5269/tcp
sudo ufw allow 5270/tcp

# TURN/STUN
sudo ufw allow 3478/udp
sudo ufw allow 5349/tcp

Services node (atl.chat)

The services node should not have any ports open to the public internet. All traffic arrives via the Tailscale tunnel from the gateway. Only allow Tailscale and SSH:
# Allow Tailscale interface
sudo ufw allow in on tailscale0

# Allow SSH (for management)
sudo ufw allow 22/tcp

# Deny everything else from public interfaces
sudo ufw default deny incoming
sudo ufw enable
The Docker daemon manages its own iptables rules for container port bindings. Since ATL_CHAT_IP is set to the Tailscale IP (100.64.7.0), container ports are only reachable from within the Tailnet.
Warning: Docker bypasses ufw rules by inserting its own iptables chains. If you bind a container port to 0.0.0.0, it will be accessible from the public internet regardless of ufw settings. Always use ATL_CHAT_IP to restrict bind addresses in production.