Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docker/backup/kopia-b2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ services:
- ${STORAGE_MEDIA}:/sources/nas/media:ro

networks:
- proxy
- backup-kopia

labels:
traefik.enable: true
traefik.docker.network: backup-kopia
traefik.http.routers.kopia-b2.middlewares: localaccess-sso@file
traefik.http.services.kopia-b2.loadbalancer.server.port: 80
homepage.group: Storage
Expand All @@ -65,5 +66,5 @@ services:
homepage.description: "[SSO] Backups Using the Cloud Storage You Pick"

networks:
proxy:
backup-kopia:
external: true
5 changes: 3 additions & 2 deletions docker/backup/kopia-nas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ services:
- ${STORAGE_BACKUP_ON_NAS}/kopia:/backup-on-nas

networks:
- proxy
- backup-kopia

labels:
traefik.enable: true
traefik.docker.network: backup-kopia
traefik.http.routers.kopia-nas.middlewares: localaccess-sso@file
traefik.http.services.kopia-nas.loadbalancer.server.port: 80
homepage.group: Storage
Expand All @@ -63,5 +64,5 @@ services:
homepage.description: "[SSO] Backups Using the Cloud Storage You Pick"

networks:
proxy:
backup-kopia:
external: true
5 changes: 3 additions & 2 deletions docker/dev/code-server.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ services:
- ${ADMIN_HOME}/repos:/config/repos
- ${ADMIN_HOME}/.ssh:/config/.ssh
networks:
- proxy
- dev-code-server
labels:
traefik.enable: true
traefik.docker.network: dev-code-server
traefik.http.routers.code.middlewares: localaccess-sso@file
traefik.http.routers.code.rule: Host(`code.${MYDOMAIN}`)
traefik.http.services.code.loadbalancer.server.port: 8443
Expand All @@ -43,5 +44,5 @@ services:
homepage.description: "[SSO] Visual Studio Code"

networks:
proxy:
dev-code-server:
external: true
67 changes: 64 additions & 3 deletions docker/guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,73 @@ networks:

### Networking

All services use a shared external `proxy` network for communication:
#### Shared Proxy Network

By default, most services connect to the shared external `proxy` network for communication:

```yaml
networks:
proxy:
external: true
```

#### Isolated Networks for Sensitive Services

Sensitive services (like backup systems, password managers, VPN services) should use isolated networks to prevent direct communication between services, with only Traefik able to connect to them:

```yaml
# Service YAML file - isolated network definition
networks:
service-category-name:
external: true

# Service configuration
services:
service:
# ... other configuration
networks:
- service-category-name # Instead of 'proxy'
labels:
traefik.enable: true
traefik.docker.network: service-category-name # Tell Traefik which network to use
# ... other labels
```

Traefik must be connected to all isolated networks to route traffic:

```yaml
# traefik.yaml
services:
traefik:
# ... other configuration
networks:
- proxy # Main network
- security-authelia # Isolated service networks
- security-wg-easy
- tools-vaultwarden
# ... other isolated networks
```

And in the networks section of the same file, all these external networks must be defined:

```yaml
networks:
proxy: # Main network
external: true
security-authelia: # Isolated service networks
external: true
security-wg-easy:
external: true
tools-vaultwarden:
external: true
# ... other isolated networks
```

This architecture ensures that:
1. Sensitive services can only communicate with Traefik, not with each other
2. Services in isolated networks are not reachable from the shared proxy network
3. All external access still flows through Traefik's security controls

### Traefik Reverse Proxy

Traefik serves as the central reverse proxy for all services with the following features:
Expand Down Expand Up @@ -178,9 +237,11 @@ services:
- ${DOCKER_VOLUMES}/service-name:/data
- ./service-name/config:/config # If needed
networks:
- proxy
- proxy # Or service-specific network for sensitive services
labels:
traefik.enable: true
# If using isolated network, specify which network Traefik should use
# traefik.docker.network: service-category-name
traefik.http.routers.service-name.middlewares: middleware-name@file
traefik.http.services.service-name.loadbalancer.server.port: PORT
homepage.group: Category
Expand All @@ -190,6 +251,6 @@ services:
homepage.description: "Service description"

networks:
proxy:
proxy: # Or service-specific network for sensitive services
external: true
```
5 changes: 3 additions & 2 deletions docker/security/authelia.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,13 @@ services:
expose:
- 9091
networks:
- proxy
- security-authelia
depends_on:
initContainer:
condition: service_completed_successfully
labels:
traefik.enable: true
traefik.docker.network: security-authelia
traefik.http.routers.authelia.rule: Host(`auth.${MYDOMAIN}`)
traefik.http.routers.authelia.middlewares: localaccess@file
traefik.http.services.authelia.loadbalancer.server.port: 9091
Expand All @@ -58,5 +59,5 @@ services:
homepage.description: "[SSO] Open-source authentication and authorization"

networks:
proxy:
security-authelia:
external: true
6 changes: 0 additions & 6 deletions docker/security/endlessh.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,8 @@ services:
ports:
- 2222:2222
# Prometheus metrics port: 2112
networks:
- proxy
labels:
homepage.group: Security
homepage.name: Endlessh
homepage.icon: terminal.png
homepage.description: "SSH tarpit (port 2222)"

networks:
proxy:
external: true
15 changes: 15 additions & 0 deletions docker/security/traefik.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ services:
- 443:443/udp # HTTP/3 - QUIC
networks:
- proxy
- security-authelia
- security-wg-easy
- dev-code-server
- tools-vaultwarden
- backup-kopia
extra_hosts:
- host.docker.internal:host-gateway
labels:
Expand Down Expand Up @@ -67,3 +72,13 @@ services:
networks:
proxy:
external: true
security-authelia:
external: true
security-wg-easy:
external: true
dev-code-server:
external: true
tools-vaultwarden:
external: true
backup-kopia:
external: true
2 changes: 1 addition & 1 deletion docker/security/traefik/traefik.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ providers:
# Docker provider for connecting all apps that are inside of the docker network
docker:
watch: true
network: proxy
network: proxy # Can be overridden on a per-container basis with the traefik.docker.network label
defaultRule: 'Host(`{{ index .Labels "com.docker.compose.service"}}.{{env "MYDOMAIN"}}`)' # Default host rule: containername.domain.tld
exposedByDefault: false # Do not expose containers to the outside world unless explicitly configured

Expand Down
5 changes: 3 additions & 2 deletions docker/security/wg-easy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ services:
volumes:
- ${DOCKER_VOLUMES}/wg-easy:/etc/wireguard
networks:
- proxy
- security-wg-easy
labels:
traefik.enable: true
traefik.docker.network: security-wg-easy
traefik.http.routers.wireguard.rule: Host(`vpn.${MYDOMAIN}`)
traefik.http.routers.wireguard.middlewares: localaccess-sso@file
traefik.http.services.wireguard.loadbalancer.server.port: 51821
Expand All @@ -45,5 +46,5 @@ services:
homepage.description: "[SSO] VPN Service"

networks:
proxy:
security-wg-easy:
external: true
5 changes: 3 additions & 2 deletions docker/tools/vaultwarden.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ services:
volumes:
- ${DOCKER_VOLUMES}/vaultwarden:/data
networks:
- proxy
- tools-vaultwarden
labels:
traefik.enable: true
traefik.docker.network: tools-vaultwarden
traefik.http.routers.vault.middlewares: localaccess@file
traefik.http.routers.vault.rule: Host(`vault.${MYDOMAIN}`)
traefik.http.services.vault.loadbalancer.server.port: 80
Expand All @@ -32,5 +33,5 @@ services:
homepage.description: Password Manager

networks:
proxy:
tools-vaultwarden:
external: true
38 changes: 37 additions & 1 deletion scripts/labctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,40 @@ def create_network_if_missing(network_name: str) -> None:
docker(["network", "create", "--driver", "bridge", network_name])


def get_external_networks(compose_file: Path) -> list[str]:
"""Extract external networks from a Docker Compose file."""
networks: list[str] = []
try:
with open(compose_file) as f:
yaml_content = yaml.safe_load(f) or {}
networks_def = yaml_content.get("networks") or {}
if isinstance(networks_def, dict):
for key, cfg in networks_def.items():
if not isinstance(cfg, dict):
continue
ext = cfg.get("external", False)
# Support: external: true | external: {name: "..."} | name: "..."
name_override = cfg.get("name")
if ext is True:
networks.append(name_override or key)
elif isinstance(ext, dict):
networks.append(ext.get("name") or name_override or key)
except (FileNotFoundError, yaml.YAMLError, OSError) as e:
logger.warning(f"Error extracting networks from {compose_file}: {e}")
return networks


def create_service_networks(compose_file: Path) -> None:
"""Create all external networks required by a service.

Args:
compose_file: Path to the Docker Compose file
"""
networks = get_external_networks(compose_file)
for network_name in networks:
create_network_if_missing(network_name)


def create_localhost_link(docker_config_dir: Path) -> None:
"""Create 'localhost' symlink in the parent directory."""
hostname = socket.gethostname()
Expand Down Expand Up @@ -111,6 +145,9 @@ def docker_command(host_config_dir: Path, stack_dir: Path, service_name: str, ac
logger.error(f"Compose file not found: {compose_file}")
return

# Ensure all required networks exist before executing any Docker Compose command
create_service_networks(compose_file)

env_file_args = get_env_file_args(host_config_dir, service_name)

# Handle other operations
Expand Down Expand Up @@ -209,7 +246,6 @@ def cmd_config_apply(args) -> None:
logger.info("Init...")
config = load_services_config(config_file)
create_localhost_link(host_config_dir.parent)
create_network_if_missing("proxy")

# Process services with optional mode override
process_services(host_config_dir, config, args.mode, args.pull_before_start)
Expand Down