aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordaltux <1medinnz@duck.com>2025-09-04 19:00:34 -0300
committerdaltux <1medinnz@duck.com>2025-09-04 19:00:34 -0300
commitffaa7aeb808c1db3594b5ad1989e20d2154d547c (patch)
treea2466245854438bff8c8d50d2db4b51d2cfc2cfe
parent4f5b49bc2476ab6986c3364aa0b88e0cc97040c4 (diff)
Examples: Docker Swarm with Traefik and container build script.
Example files for setting a complete SNAC + Traefik v3 stack on Docker Swarm mode, including Let's Encrypt certificates, HTTP security headers, and caching of */s/* (SNAC static files) for 24 hours with souin. A script for building the SNAC container with Docker and optionally pushing it to a registry.
-rw-r--r--examples/docker-swarm-traefik/snac_stack.yml114
-rw-r--r--examples/docker-swarm-traefik/traefik_config.yml89
-rwxr-xr-xexamples/docker_build_snac.sh44
3 files changed, 247 insertions, 0 deletions
diff --git a/examples/docker-swarm-traefik/snac_stack.yml b/examples/docker-swarm-traefik/snac_stack.yml
new file mode 100644
index 0000000..e799475
--- /dev/null
+++ b/examples/docker-swarm-traefik/snac_stack.yml
@@ -0,0 +1,114 @@
+---
+# Example of a SNAC + Traefik stack on Docker Swarm mode
+# SNAC behind Traefik v3 reverse proxy
+# including Let's Encrypt certificates, HTTP security headers
+# and caching */s/* (SNAC static files) for 24h with souin.
+#
+# docker stack deploy -c snac_stack.yml snac
+
+services:
+ traefik:
+ image: traefik:v3
+ hostname: '{{.Node.Hostname}}'
+ ports:
+ # listen on host ports without ingress network
+ - target: 80
+ published: 80
+ protocol: tcp
+ mode: host
+ - target: 443
+ published: 443
+ protocol: tcp
+ mode: host
+ - target: 443
+ published: 443
+ protocol: udp # for HTTP3
+ mode: host
+ networks:
+ - proxy
+ volumes:
+ - /opt/docker/traefik/traefik.yml:/etc/traefik/traefik_config.yml # main Traefik static configuration
+ - /opt/docker/traefik/dynamic:/etc/traefik/dynamic # for Traefik dynamic configuration files
+ - /opt/docker/acme:/acme # directory for certificate storage files
+ - /opt/docker/traefik/log:/var/log
+ - /var/run/docker.sock:/var/run/docker.sock:ro
+ - /usr/share/zoneinfo/America/Sao_Paulo:/etc/localtime:ro # Change this to another or comment/remove to use UTC
+ - /etc/timezone:/etc/timezone:ro # Comment/remove to use UTC
+ environment:
+ - TZ=America/Sao_Paulo # Change it to another or comment/remove to use UTC
+ deploy:
+ mode: global
+ placement:
+ constraints:
+ - node.role==manager
+ labels:
+ - traefik.enable=true
+ - traefik.http.routers.mydashboard.rule=Host(`traefik.example.net`) # for Traefik dashboard
+ - traefik.http.routers.mydashboard.service=api@internal
+ - traefik.http.routers.mydashboard.middlewares=traefik_auth,traefik_retry
+ - traefik.http.services.mydashboard.loadbalancer.server.port=1337
+ - traefik.http.middlewares.traefik_retry.retry.attempts=3
+ - traefik.http.middlewares.traefik_retry.retry.initialinterval=200ms
+ # How to generate a password hash: echo $(htpasswd -nB desired_username) | sed -e s/\\$/\\$\\$/g
+ - traefik.http.middlewares.traefik_auth.basicauth.users=desired_username:PASTE_THE_PASSWORD_HASH_HERE
+ resources:
+ limits:
+ cpus: "2"
+ memory: 1g
+ reservations:
+ memory: 256m
+
+ snac:
+ image: codeberg.org/daltux/snac:2.82-dev_daltux3
+ hostname: '{{.Node.Hostname}}'
+ restart: unless-stopped
+ security_opt:
+ - no-new-privileges:true
+ volumes:
+ - /opt/snac:/data
+ networks:
+ - proxy
+ depends_on:
+ - traefik
+ environment:
+ - TZ=America/Sao_Paulo # Change this to your own or comment/remove it to use UTC.
+ deploy:
+ mode: replicated
+ replicas: 1
+ labels:
+ - traefik.enable=true
+ - traefik.http.services.snac.loadbalancer.server.port=8001
+ - traefik.http.routers.snac.rule=Host(`snac.example.net`)
+ - traefik.http.routers.snac.priority=1
+ - traefik.http.routers.snac.middlewares=snac_headers@swarm,traefik_retry@swarm
+ - "traefik.http.routers.snac_static.rule=Host(`snac.example.net`) && PathRegexp(`^/.+/s/.+`)"
+ - "traefik.http.routers.snac_static.priority=2"
+ - "traefik.http.routers.snac_static.middlewares=snac_headers@swarm,snac_cache_static@swarm,traefik_retry@swarm"
+ - traefik.http.middlewares.snac_headers.headers.stsSeconds=31536000
+ - traefik.http.middlewares.snac_headers.headers.stsIncludeSubdomains=false
+ - traefik.http.middlewares.snac_headers.headers.stsPreload=false
+ - traefik.http.middlewares.snac_headers.headers.customFrameOptionsValue=SAMEORIGIN
+ - traefik.http.middlewares.snac_headers.headers.contentTypeNosniff=true
+ - traefik.http.middlewares.snac_headers.headers.browserXssFilter=true
+ - traefik.http.middlewares.snac_headers.headers.referrerPolicy=strict-origin-when-cross-origin
+ - "traefik.http.middlewares.snac_headers.headers.contentSecurityPolicy=default-src 'self'; base-uri 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; form-action 'self'; img-src https: data:; font-src 'self'; media-src https: data:; connect-src 'none'; frame-src 'none'; frame-ancestors 'none';"
+ - traefik.http.middlewares.snac_cache_static.plugin.souin.api.souin=true
+ - traefik.http.middlewares.snac_cache_static.plugin.souin.log_level=info
+ - traefik.http.middlewares.snac_cache_static.plugin.souin.default_cache.ttl=1440m
+ - traefik.http.middlewares.snac_cache_static.plugin.souin.default_cache.allowed_http_verbs=GET,HEAD
+ - traefik.http.middlewares.snac_cache_static.plugin.souin.default_cache.stale=1h
+ - traefik.http.middlewares.snac_cache_static.plugin.souin.default_cache.etag=true
+ - "traefik.http.middlewares.snac_cache_static.plugin.souin.default_cache.default_cache_control=public, max-age=86400"
+ resources:
+ limits:
+ cpus: "3"
+ memory: 1g
+ reservations:
+ memory: 256m
+
+networks:
+ proxy:
+ name: proxy
+ driver: overlay
+ attachable: true
+ external: true
diff --git a/examples/docker-swarm-traefik/traefik_config.yml b/examples/docker-swarm-traefik/traefik_config.yml
new file mode 100644
index 0000000..045408c
--- /dev/null
+++ b/examples/docker-swarm-traefik/traefik_config.yml
@@ -0,0 +1,89 @@
+---
+# Traefik main config file
+# e.g. /opt/docker/traefik/traefik_config.yml
+
+entryPoints:
+ web:
+ address: ":80"
+ http:
+ encodeQuerySemicolons: true
+ redirections:
+ entryPoint:
+ to: websecure
+ scheme: https
+ permanent: true
+ websecure:
+ address: ":443"
+ asDefault: true
+ http:
+ encodeQuerySemicolons: true
+ tls:
+ certResolver: letsencrypt
+ http2:
+ maxConcurrentStreams: 100
+ http3: {}
+
+certificatesResolvers:
+ letsencrypt:
+ acme:
+ email: you@example.net
+ storage: "/acme/letsencrypt.json"
+ keyType: EC384
+ httpChallenge:
+ entryPoint: web
+# buypass:
+# acme:
+# email: you@example.net
+# caServer: "https://api.buypass.com/acme/directory"
+# storage: "/acme/buypass.json"
+# keyType: EC256
+# certificatesDuration: 4320
+# httpChallenge:
+# entryPoint: web
+
+ocsp: {}
+
+tls:
+ stores:
+ default:
+ defaultGeneratedCert:
+ resolver: letsencrypt
+ domain:
+ main: snac.example.net
+ # sans:
+ # - other.example.net
+ # - another.example.net
+
+providers:
+ file:
+ directory: "/etc/traefik/dynamic"
+ watch: true
+ swarm:
+ network: "proxy"
+ endpoint: "unix:///var/run/docker.sock"
+ exposedByDefault: false
+ watch: true
+ allowEmptyServices: true
+
+api:
+ dashboard: true
+ insecure: false
+ debug: false
+ disabledashboardad: true
+
+log:
+ level: "INFO"
+ filePath: "/var/log/server.log"
+
+accessLog:
+ filePath: "/var/log/traefik-access.log"
+ bufferingSize: 15
+ fields:
+ names:
+ StartUTC: "drop"
+
+experimental:
+ plugins:
+ souin:
+ moduleName: "github.com/darkweak/souin"
+ version: "v1.7.7"
diff --git a/examples/docker_build_snac.sh b/examples/docker_build_snac.sh
new file mode 100755
index 0000000..387aa49
--- /dev/null
+++ b/examples/docker_build_snac.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+#
+# docker_build_snac.sh : build a SNAC container image
+# and optionally send it to a container registry.
+#
+# Set variables e.g. if you want version to be different from 'latest':
+# img_version=2.82 ./docker_build_snac.sh
+
+src_dir=${src_dir:-"$HOME/src/snac2"}
+img_name=${img_name:-'snac'}
+img_version=${img_version:-'latest'}
+#registry=${registry:-'codeberg.org'}
+#reg_user=${reg_user:-'daltux'}
+
+if [ -z "$tag" ] ; then
+ if [ -n "$reg_user" ] && [ -z "$registry" ] ; then
+ echo "Missing container registry name. Set variable \"registry\"." >&2
+ exit 10
+ fi
+
+ if [ -z "$registry" ] ; then
+ tag="$img_name:$img_version"
+ elif [ -z "$reg_user" ] ; then
+ echo "Container registry user unknown. Set variable \"reg_user\"." >&2
+ exit 20
+ else
+ tag="$registry/$reg_user/$img_name:$img_version"
+ fi
+fi
+
+if [ -d "$src_dir" ] ; then
+ echo "Entering directory \"$src_dir\"..."
+ cd "$src_dir" || exit $?
+ docker build --no-cache -f Dockerfile -t "$tag" . || exit $?
+else
+ echo "Invalid directory \"$src_dir\"" >&2
+ exit 30
+fi
+
+if [ -n "$registry" ] ; then
+ #docker login "$registry" || $?
+ docker image push "$tag"
+fi
+