feat: add traefik tcp load balancer

This commit is contained in:
Alfredo Di Stasio
2026-06-30 09:50:18 +02:00
parent 786fa78ebd
commit 7dfc23d69e
13 changed files with 393 additions and 129 deletions
+4 -7
View File
@@ -4,9 +4,9 @@ Edit this file for each repository.
## Project identity
Project name: `CHANGE_ME`
Project description: `CHANGE_ME`
Primary language/runtime: `CHANGE_ME`
Project name: `traefik-tcp-loadbalancer`
Project description: `Dockerized Traefik TCP load balancer with TLS passthrough`
Primary language/runtime: `Docker Compose / Traefik`
## Project mode
@@ -14,7 +14,6 @@ Choose one:
```text
project_mode: personal
project_mode: work
```
Rules:
@@ -29,8 +28,6 @@ Enable only the profiles that apply to this repository:
```text
enabled_profiles:
- docker
- ansible
- python
```
## Branching model
@@ -77,7 +74,7 @@ All tests MUST be executed inside Docker containers.
Configure the canonical test command for this repository:
```bash
CHANGE_ME
docker compose config
```
Examples:
+4
View File
@@ -0,0 +1,4 @@
*.log
.env
config/**/*.bak
config/**/*.tmp
+51 -61
View File
@@ -1,73 +1,63 @@
# codex-bootstrap
# Traefik TCP Load Balancer
A repository template for AI-assisted development with Codex.
Docker Compose deployment for Traefik used as a TCP load balancer with TLS passthrough.
This template defines a repeatable workflow for using Codex as an autonomous coding agent that can create branches, modify code, run Docker-based tests, update documentation, write ADRs, and commit changes using Conventional Commits.
## What it does
## Purpose
- publishes `443/tcp` on the Docker host;
- forwards TCP TLS traffic to backend IP addresses and ports;
- leaves SSL certificates and private keys on the backends;
- stores Traefik configuration in the host `./config` directory;
- uses the Traefik file provider, without mounting the Docker socket.
Use this template to bootstrap repositories where Codex must operate with clear rules, minimal changes, pragmatic TDD, security guardrails, and explicit documentation requirements.
## Configuration
## Repository structure
Edit backend targets in `config/dynamic/tcp-services.yml`:
```text
.
├── AGENTS.md
├── README.md
├── .codex/
│ ├── project.md
│ ├── workflow.md
│ ├── security.md
│ ├── quality.md
│ ├── orchestration.md
│ ├── prompts/
│ │ ├── task.md
│ │ ├── bugfix.md
│ │ ├── refactor.md
│ │ ├── security-review.md
│ │ └── documentation.md
│ ├── agents/
│ │ ├── architect.md
│ │ ├── developer.md
│ │ ├── reviewer.md
│ │ ├── security-reviewer.md
│ │ ├── test-engineer.md
│ │ └── documentation-writer.md
│ └── profiles/
│ ├── docker.md
│ ├── ansible.md
│ └── python.md
└── docs/
├── adr/
│ └── 0000-template.md
├── architecture.md
├── deployment.md
├── operations.md
├── security.md
├── testing.md
└── runbook.md
```yaml
tcp:
services:
ssl-backends:
loadBalancer:
servers:
- address: "192.0.2.10:443"
- address: "192.0.2.11:443"
```
## How to use
The example addresses use the documentation-only `192.0.2.0/24` range. Replace them with the real backend IP addresses and ports before deploying. If a backend listens on the Docker host itself, use `host.docker.internal:PORT`.
1. Copy this template into a new or existing repository.
2. Edit `.codex/project.md` and configure:
- project mode;
- enabled profiles;
- Docker-based test command;
- branch naming rules if needed.
3. Add project-specific details to the documentation under `docs/`.
4. When asking Codex to work on a task, use one of the prompt templates under `.codex/prompts/`.
## Run
## Core rules
Validate the Compose file:
Codex must:
```bash
docker compose config
```
- start work from `develop`;
- create a dedicated `feature/`, `fix/`, or `hotfix/` branch;
- use pragmatic TDD;
- keep changes minimal and focused;
- run the configured Docker-based test command before completion;
- update documentation and ADRs when needed;
- produce a final report with summary, tests, risks, and rollback notes;
- commit using Conventional Commits.
Start Traefik:
```bash
docker compose up -d
```
View logs:
```bash
docker compose logs -f traefik
```
Stop the service:
```bash
docker compose down
```
## Documentation
- Architecture: `docs/architecture.md`
- Deployment: `docs/deployment.md`
- Operations: `docs/operations.md`
- Security: `docs/security.md`
- Testing: `docs/testing.md`
- Runbook: `docs/runbook.md`
- ADR: `docs/adr/0001-traefik-tcp-tls-passthrough.md`
+24
View File
@@ -0,0 +1,24 @@
services:
traefik:
image: traefik:v3.7.5
container_name: tcp-loadbalancer-traefik
restart: unless-stopped
command:
- --configFile=/etc/traefik/traefik.yml
ports:
- "443:8443/tcp"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- ./config:/etc/traefik:ro
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD", "traefik", "healthcheck", "--ping"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
+16
View File
@@ -0,0 +1,16 @@
tcp:
routers:
tls-passthrough:
entryPoints:
- tls
rule: "HostSNI(`*`)"
service: ssl-backends
tls:
passthrough: true
services:
ssl-backends:
loadBalancer:
servers:
- address: "192.0.2.10:443"
- address: "192.0.2.11:443"
+21
View File
@@ -0,0 +1,21 @@
global:
checkNewVersion: false
sendAnonymousUsage: false
entryPoints:
tls:
address: ":8443"
ping:
address: ":8082"
providers:
file:
directory: /etc/traefik/dynamic
watch: true
ping:
entryPoint: ping
log:
level: INFO
format: common
@@ -0,0 +1,39 @@
# ADR-0001: Traefik TCP TLS passthrough load balancer
Date: 2026-06-30
Status: Accepted
## Context
The load balancer must run as a Docker container and forward TCP TLS traffic to backend services reachable by local IP address and port. Backend services already expose their own SSL certificates, so the load balancer must not terminate TLS or store backend private keys. Runtime configuration must be kept in a host-based volume.
## Decision
Use Traefik Proxy in Docker with the file provider and TCP TLS passthrough routing. Static and dynamic configuration live under the host `./config` directory and are mounted read-only at `/etc/traefik`.
The public host port `443/tcp` is mapped to container port `8443/tcp` so the container can run with all Linux capabilities dropped. Traefik routes incoming TLS connections with `HostSNI(*)` to the configured TCP load-balancer service.
## Consequences
TLS certificates, private keys, and certificate renewal remain the responsibility of each backend. Traefik can route based on the TLS SNI value but cannot inspect HTTP headers or paths because TLS is passed through unchanged.
Backend health is limited to TCP reachability unless protocol-specific health checks are added later. Configuration changes in `config/dynamic/` are watched by Traefik and can be applied without recreating the container.
## Alternatives considered
TLS termination at Traefik was rejected because the requested backend-owned certificate model would require moving certificates or keys to Traefik.
The Docker provider was not selected because the backends are addressed directly by IP and port rather than discovered from Docker labels.
## Security impact
The container does not mount the Docker socket, does not run privileged, drops Linux capabilities, uses `no-new-privileges`, and mounts configuration read-only. No private keys or credentials are required in this repository.
## Operational impact
Operators update backend IP addresses and ports in `config/dynamic/tcp-services.yml`. The Compose deployment publishes only `443/tcp`; the ping entrypoint is internal to the container and used by the healthcheck.
## Rollback
Revert the commit that introduced the Traefik configuration and restore any previous load-balancer service. If deployed, stop the Compose stack with `docker compose down`.
+25 -9
View File
@@ -1,13 +1,29 @@
# Architecture
Describe the project architecture here.
This project runs Traefik Proxy as a Dockerized TCP load balancer.
Include:
## Components
- main components;
- runtime dependencies;
- data flow;
- persistence;
- external integrations;
- deployment topology;
- relevant ADRs.
- `compose.yaml`: defines the Traefik container, published port, host-based configuration volume, container hardening, and healthcheck.
- `config/traefik.yml`: static Traefik configuration for entrypoints, file provider, ping, and logging.
- `config/dynamic/tcp-services.yml`: dynamic TCP router and backend load-balancer service.
## Data flow
1. A client opens a TLS connection to the Docker host on `443/tcp`.
2. Docker forwards the connection to Traefik on container port `8443/tcp`.
3. Traefik matches the TCP router using `HostSNI(*)`.
4. Traefik forwards the unchanged TCP stream to one configured backend IP address and port.
5. The selected backend terminates TLS and presents its own SSL certificate.
## Persistence
Traefik configuration is stored in the host `./config` directory and mounted read-only at `/etc/traefik`.
No certificates, private keys, application data, or Docker socket are mounted into the Traefik container.
## Deployment topology
Traefik runs as one Docker container on the load-balancer host. Backends are external to this Compose stack and must be reachable from the container through their configured IP addresses and ports. The Compose service defines `host.docker.internal` through Docker's `host-gateway` feature for backends that listen on the Docker host itself.
See `docs/adr/0001-traefik-tcp-tls-passthrough.md` for the design decision.
+58 -11
View File
@@ -1,15 +1,62 @@
# Deployment
Describe how this project is deployed.
## Requirements
Include:
- Docker Engine with Docker Compose plugin.
- Backend services reachable from the Docker host or container by IP address and port.
- Backend services must terminate TLS and present valid SSL certificates for the expected client names.
- environments;
- Docker/Compose usage;
- required configuration;
- secrets handling;
- exposed ports;
- volumes;
- networks;
- deployment commands;
- rollback procedure.
## Configuration
Update `config/dynamic/tcp-services.yml` with the real backend addresses:
```yaml
servers:
- address: "10.0.0.10:443"
- address: "10.0.0.11:443"
```
If a backend listens on the Docker host, use `host.docker.internal:PORT`. Compose maps that name through Docker's `host-gateway` feature.
The `./config` directory is the host-based configuration volume. It is mounted read-only into the container:
```yaml
volumes:
- ./config:/etc/traefik:ro
```
## Ports
- Host `443/tcp` -> container `8443/tcp`: public TCP TLS passthrough load-balancer entrypoint.
- Container `8082/tcp`: internal Traefik ping entrypoint used only by the container healthcheck.
## Deploy
Validate the Compose configuration:
```bash
docker compose config
```
Start the stack:
```bash
docker compose up -d
```
Check status:
```bash
docker compose ps
docker compose logs traefik
```
## Rollback
Stop the stack:
```bash
docker compose down
```
Then revert the deployment commit or restore the previous load-balancer configuration.
+53 -9
View File
@@ -1,13 +1,57 @@
# Operations
Describe operational procedures.
## Startup and shutdown
Include:
Start Traefik:
- startup and shutdown;
- health checks;
- logs;
- monitoring;
- backup and restore;
- routine maintenance;
- known operational risks.
```bash
docker compose up -d
```
Stop Traefik:
```bash
docker compose down
```
Restart after static configuration changes in `config/traefik.yml`:
```bash
docker compose restart traefik
```
Dynamic configuration changes under `config/dynamic/` are watched by Traefik and normally do not require a restart.
## Health checks
The container healthcheck runs:
```bash
traefik healthcheck --ping
```
Check health with:
```bash
docker compose ps
```
## Logs
Traefik logs to container stdout:
```bash
docker compose logs -f traefik
```
## Backend maintenance
To remove a backend from rotation, edit `config/dynamic/tcp-services.yml` and remove or comment out its `address` entry. Keep at least one reachable backend configured before applying the change.
For backends running on the Docker host, use `host.docker.internal:PORT` rather than `127.0.0.1:PORT`, because `127.0.0.1` inside the container refers to the container itself.
## Known operational risks
- TLS passthrough prevents Traefik from inspecting HTTP paths, headers, or certificate contents.
- Backend certificates and renewal jobs must be monitored on each backend.
- The default configuration routes all SNI names to the same backend pool. Use more specific `HostSNI(...)` rules if different names need different pools.
+46 -7
View File
@@ -1,19 +1,58 @@
# Runbook
Operational runbook for this project.
## Common tasks
Document routine operational tasks here.
### Start the load balancer
```bash
docker compose up -d
```
### Stop the load balancer
```bash
docker compose down
```
### Update backend targets
1. Edit `config/dynamic/tcp-services.yml`.
2. Run `docker compose config` to catch Compose-level errors.
3. Watch Traefik logs:
```bash
docker compose logs -f traefik
```
Dynamic file-provider changes are watched by Traefik.
## Troubleshooting
Document known issues, symptoms, checks, and remediation steps.
### Container is unhealthy
Check container status and logs:
```bash
docker compose ps
docker compose logs traefik
```
Confirm that `config/traefik.yml` is mounted and that the ping entrypoint is enabled.
### Clients cannot connect
Check that the host is listening on `443/tcp`, firewall rules allow inbound traffic, and backend IP addresses are reachable from the Traefik container. For services on the Docker host, configure backends as `host.docker.internal:PORT`.
### Wrong certificate is presented
Because TLS is passed through, the certificate comes from the selected backend. Check backend certificate configuration and SNI routing rules in `config/dynamic/tcp-services.yml`.
## Rollback
Document rollback procedures here.
Stop the Compose stack:
## Emergency contacts
```bash
docker compose down
```
Document project-specific escalation paths if appropriate.
Revert the deployment commit or restore the previous load-balancer configuration.
+35 -12
View File
@@ -1,16 +1,39 @@
# Security
Describe security assumptions and controls.
## TLS and certificates
Include:
Traefik is configured for TCP TLS passthrough. It does not terminate TLS and does not require certificate files or private keys.
- authentication;
- authorization;
- network exposure;
- TLS/certificates;
- secrets management;
- logging of sensitive data;
- container privileges;
- filesystem permissions;
- dependency management;
- relevant ADRs.
Backend services are responsible for:
- presenting SSL certificates;
- protecting private keys;
- renewing certificates;
- enforcing any application-layer authentication and authorization.
## Network exposure
Only `443/tcp` is published by Compose. The Traefik ping entrypoint listens inside the container for healthchecks and is not published on the host.
## Container hardening
The Traefik service:
- does not mount the Docker socket;
- is not privileged;
- drops Linux capabilities;
- enables `no-new-privileges`;
- mounts configuration read-only;
- uses an explicit Traefik image tag instead of `latest`.
## Secrets
No secrets should be stored in this repository. Do not add certificates, private keys, API tokens, or passwords to `config/` or documentation.
## Logging
Traefik logs operational events to stdout. Do not enable debug logging in production unless needed for a time-limited investigation.
## Relevant ADRs
- `docs/adr/0001-traefik-tcp-tls-passthrough.md`
+17 -13
View File
@@ -1,23 +1,27 @@
# Testing
Describe how tests are executed.
All tests should run inside Docker containers.
All project validation should run through Docker-based commands.
## Canonical test command
The canonical project validation command is:
```bash
CHANGE_ME
docker compose config
```
## Smoke test
After replacing the example backend addresses and starting the stack, verify TCP connectivity from a client:
```bash
openssl s_client -connect LOAD_BALANCER_HOST:443 -servername EXPECTED_DNS_NAME
```
The certificate shown by `openssl` should be the backend certificate, not a certificate stored by Traefik.
## Test categories
Describe applicable categories:
- unit tests;
- integration tests;
- linting;
- formatting checks;
- Ansible syntax checks;
- Docker/Compose validation;
- smoke tests.
- Docker/Compose validation: `docker compose config`.
- Runtime health: `docker compose ps`.
- TLS passthrough smoke test: `openssl s_client`.