diff options
| author | 2025-09-04 19:00:34 -0300 | |
|---|---|---|
| committer | 2025-09-04 19:00:34 -0300 | |
| commit | ffaa7aeb808c1db3594b5ad1989e20d2154d547c (patch) | |
| tree | a2466245854438bff8c8d50d2db4b51d2cfc2cfe | |
| parent | Merge branch 'master' of https://codeberg.org/daltux/snac2 into docker_tzdata (diff) | |
| download | snac2-ffaa7aeb808c1db3594b5ad1989e20d2154d547c.tar.gz snac2-ffaa7aeb808c1db3594b5ad1989e20d2154d547c.tar.xz snac2-ffaa7aeb808c1db3594b5ad1989e20d2154d547c.zip | |
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.yml | 114 | ||||
| -rw-r--r-- | examples/docker-swarm-traefik/traefik_config.yml | 89 | ||||
| -rwxr-xr-x | examples/docker_build_snac.sh | 44 |
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 @@ | |||
| 1 | --- | ||
| 2 | # Example of a SNAC + Traefik stack on Docker Swarm mode | ||
| 3 | # SNAC behind Traefik v3 reverse proxy | ||
| 4 | # including Let's Encrypt certificates, HTTP security headers | ||
| 5 | # and caching */s/* (SNAC static files) for 24h with souin. | ||
| 6 | # | ||
| 7 | # docker stack deploy -c snac_stack.yml snac | ||
| 8 | |||
| 9 | services: | ||
| 10 | traefik: | ||
| 11 | image: traefik:v3 | ||
| 12 | hostname: '{{.Node.Hostname}}' | ||
| 13 | ports: | ||
| 14 | # listen on host ports without ingress network | ||
| 15 | - target: 80 | ||
| 16 | published: 80 | ||
| 17 | protocol: tcp | ||
| 18 | mode: host | ||
| 19 | - target: 443 | ||
| 20 | published: 443 | ||
| 21 | protocol: tcp | ||
| 22 | mode: host | ||
| 23 | - target: 443 | ||
| 24 | published: 443 | ||
| 25 | protocol: udp # for HTTP3 | ||
| 26 | mode: host | ||
| 27 | networks: | ||
| 28 | - proxy | ||
| 29 | volumes: | ||
| 30 | - /opt/docker/traefik/traefik.yml:/etc/traefik/traefik_config.yml # main Traefik static configuration | ||
| 31 | - /opt/docker/traefik/dynamic:/etc/traefik/dynamic # for Traefik dynamic configuration files | ||
| 32 | - /opt/docker/acme:/acme # directory for certificate storage files | ||
| 33 | - /opt/docker/traefik/log:/var/log | ||
| 34 | - /var/run/docker.sock:/var/run/docker.sock:ro | ||
| 35 | - /usr/share/zoneinfo/America/Sao_Paulo:/etc/localtime:ro # Change this to another or comment/remove to use UTC | ||
| 36 | - /etc/timezone:/etc/timezone:ro # Comment/remove to use UTC | ||
| 37 | environment: | ||
| 38 | - TZ=America/Sao_Paulo # Change it to another or comment/remove to use UTC | ||
| 39 | deploy: | ||
| 40 | mode: global | ||
| 41 | placement: | ||
| 42 | constraints: | ||
| 43 | - node.role==manager | ||
| 44 | labels: | ||
| 45 | - traefik.enable=true | ||
| 46 | - traefik.http.routers.mydashboard.rule=Host(`traefik.example.net`) # for Traefik dashboard | ||
| 47 | - traefik.http.routers.mydashboard.service=api@internal | ||
| 48 | - traefik.http.routers.mydashboard.middlewares=traefik_auth,traefik_retry | ||
| 49 | - traefik.http.services.mydashboard.loadbalancer.server.port=1337 | ||
| 50 | - traefik.http.middlewares.traefik_retry.retry.attempts=3 | ||
| 51 | - traefik.http.middlewares.traefik_retry.retry.initialinterval=200ms | ||
| 52 | # How to generate a password hash: echo $(htpasswd -nB desired_username) | sed -e s/\\$/\\$\\$/g | ||
| 53 | - traefik.http.middlewares.traefik_auth.basicauth.users=desired_username:PASTE_THE_PASSWORD_HASH_HERE | ||
| 54 | resources: | ||
| 55 | limits: | ||
| 56 | cpus: "2" | ||
| 57 | memory: 1g | ||
| 58 | reservations: | ||
| 59 | memory: 256m | ||
| 60 | |||
| 61 | snac: | ||
| 62 | image: codeberg.org/daltux/snac:2.82-dev_daltux3 | ||
| 63 | hostname: '{{.Node.Hostname}}' | ||
| 64 | restart: unless-stopped | ||
| 65 | security_opt: | ||
| 66 | - no-new-privileges:true | ||
| 67 | volumes: | ||
| 68 | - /opt/snac:/data | ||
| 69 | networks: | ||
| 70 | - proxy | ||
| 71 | depends_on: | ||
| 72 | - traefik | ||
| 73 | environment: | ||
| 74 | - TZ=America/Sao_Paulo # Change this to your own or comment/remove it to use UTC. | ||
| 75 | deploy: | ||
| 76 | mode: replicated | ||
| 77 | replicas: 1 | ||
| 78 | labels: | ||
| 79 | - traefik.enable=true | ||
| 80 | - traefik.http.services.snac.loadbalancer.server.port=8001 | ||
| 81 | - traefik.http.routers.snac.rule=Host(`snac.example.net`) | ||
| 82 | - traefik.http.routers.snac.priority=1 | ||
| 83 | - traefik.http.routers.snac.middlewares=snac_headers@swarm,traefik_retry@swarm | ||
| 84 | - "traefik.http.routers.snac_static.rule=Host(`snac.example.net`) && PathRegexp(`^/.+/s/.+`)" | ||
| 85 | - "traefik.http.routers.snac_static.priority=2" | ||
| 86 | - "traefik.http.routers.snac_static.middlewares=snac_headers@swarm,snac_cache_static@swarm,traefik_retry@swarm" | ||
| 87 | - traefik.http.middlewares.snac_headers.headers.stsSeconds=31536000 | ||
| 88 | - traefik.http.middlewares.snac_headers.headers.stsIncludeSubdomains=false | ||
| 89 | - traefik.http.middlewares.snac_headers.headers.stsPreload=false | ||
| 90 | - traefik.http.middlewares.snac_headers.headers.customFrameOptionsValue=SAMEORIGIN | ||
| 91 | - traefik.http.middlewares.snac_headers.headers.contentTypeNosniff=true | ||
| 92 | - traefik.http.middlewares.snac_headers.headers.browserXssFilter=true | ||
| 93 | - traefik.http.middlewares.snac_headers.headers.referrerPolicy=strict-origin-when-cross-origin | ||
| 94 | - "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';" | ||
| 95 | - traefik.http.middlewares.snac_cache_static.plugin.souin.api.souin=true | ||
| 96 | - traefik.http.middlewares.snac_cache_static.plugin.souin.log_level=info | ||
| 97 | - traefik.http.middlewares.snac_cache_static.plugin.souin.default_cache.ttl=1440m | ||
| 98 | - traefik.http.middlewares.snac_cache_static.plugin.souin.default_cache.allowed_http_verbs=GET,HEAD | ||
| 99 | - traefik.http.middlewares.snac_cache_static.plugin.souin.default_cache.stale=1h | ||
| 100 | - traefik.http.middlewares.snac_cache_static.plugin.souin.default_cache.etag=true | ||
| 101 | - "traefik.http.middlewares.snac_cache_static.plugin.souin.default_cache.default_cache_control=public, max-age=86400" | ||
| 102 | resources: | ||
| 103 | limits: | ||
| 104 | cpus: "3" | ||
| 105 | memory: 1g | ||
| 106 | reservations: | ||
| 107 | memory: 256m | ||
| 108 | |||
| 109 | networks: | ||
| 110 | proxy: | ||
| 111 | name: proxy | ||
| 112 | driver: overlay | ||
| 113 | attachable: true | ||
| 114 | 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 @@ | |||
| 1 | --- | ||
| 2 | # Traefik main config file | ||
| 3 | # e.g. /opt/docker/traefik/traefik_config.yml | ||
| 4 | |||
| 5 | entryPoints: | ||
| 6 | web: | ||
| 7 | address: ":80" | ||
| 8 | http: | ||
| 9 | encodeQuerySemicolons: true | ||
| 10 | redirections: | ||
| 11 | entryPoint: | ||
| 12 | to: websecure | ||
| 13 | scheme: https | ||
| 14 | permanent: true | ||
| 15 | websecure: | ||
| 16 | address: ":443" | ||
| 17 | asDefault: true | ||
| 18 | http: | ||
| 19 | encodeQuerySemicolons: true | ||
| 20 | tls: | ||
| 21 | certResolver: letsencrypt | ||
| 22 | http2: | ||
| 23 | maxConcurrentStreams: 100 | ||
| 24 | http3: {} | ||
| 25 | |||
| 26 | certificatesResolvers: | ||
| 27 | letsencrypt: | ||
| 28 | acme: | ||
| 29 | email: you@example.net | ||
| 30 | storage: "/acme/letsencrypt.json" | ||
| 31 | keyType: EC384 | ||
| 32 | httpChallenge: | ||
| 33 | entryPoint: web | ||
| 34 | # buypass: | ||
| 35 | # acme: | ||
| 36 | # email: you@example.net | ||
| 37 | # caServer: "https://api.buypass.com/acme/directory" | ||
| 38 | # storage: "/acme/buypass.json" | ||
| 39 | # keyType: EC256 | ||
| 40 | # certificatesDuration: 4320 | ||
| 41 | # httpChallenge: | ||
| 42 | # entryPoint: web | ||
| 43 | |||
| 44 | ocsp: {} | ||
| 45 | |||
| 46 | tls: | ||
| 47 | stores: | ||
| 48 | default: | ||
| 49 | defaultGeneratedCert: | ||
| 50 | resolver: letsencrypt | ||
| 51 | domain: | ||
| 52 | main: snac.example.net | ||
| 53 | # sans: | ||
| 54 | # - other.example.net | ||
| 55 | # - another.example.net | ||
| 56 | |||
| 57 | providers: | ||
| 58 | file: | ||
| 59 | directory: "/etc/traefik/dynamic" | ||
| 60 | watch: true | ||
| 61 | swarm: | ||
| 62 | network: "proxy" | ||
| 63 | endpoint: "unix:///var/run/docker.sock" | ||
| 64 | exposedByDefault: false | ||
| 65 | watch: true | ||
| 66 | allowEmptyServices: true | ||
| 67 | |||
| 68 | api: | ||
| 69 | dashboard: true | ||
| 70 | insecure: false | ||
| 71 | debug: false | ||
| 72 | disabledashboardad: true | ||
| 73 | |||
| 74 | log: | ||
| 75 | level: "INFO" | ||
| 76 | filePath: "/var/log/server.log" | ||
| 77 | |||
| 78 | accessLog: | ||
| 79 | filePath: "/var/log/traefik-access.log" | ||
| 80 | bufferingSize: 15 | ||
| 81 | fields: | ||
| 82 | names: | ||
| 83 | StartUTC: "drop" | ||
| 84 | |||
| 85 | experimental: | ||
| 86 | plugins: | ||
| 87 | souin: | ||
| 88 | moduleName: "github.com/darkweak/souin" | ||
| 89 | 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 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | # | ||
| 3 | # docker_build_snac.sh : build a SNAC container image | ||
| 4 | # and optionally send it to a container registry. | ||
| 5 | # | ||
| 6 | # Set variables e.g. if you want version to be different from 'latest': | ||
| 7 | # img_version=2.82 ./docker_build_snac.sh | ||
| 8 | |||
| 9 | src_dir=${src_dir:-"$HOME/src/snac2"} | ||
| 10 | img_name=${img_name:-'snac'} | ||
| 11 | img_version=${img_version:-'latest'} | ||
| 12 | #registry=${registry:-'codeberg.org'} | ||
| 13 | #reg_user=${reg_user:-'daltux'} | ||
| 14 | |||
| 15 | if [ -z "$tag" ] ; then | ||
| 16 | if [ -n "$reg_user" ] && [ -z "$registry" ] ; then | ||
| 17 | echo "Missing container registry name. Set variable \"registry\"." >&2 | ||
| 18 | exit 10 | ||
| 19 | fi | ||
| 20 | |||
| 21 | if [ -z "$registry" ] ; then | ||
| 22 | tag="$img_name:$img_version" | ||
| 23 | elif [ -z "$reg_user" ] ; then | ||
| 24 | echo "Container registry user unknown. Set variable \"reg_user\"." >&2 | ||
| 25 | exit 20 | ||
| 26 | else | ||
| 27 | tag="$registry/$reg_user/$img_name:$img_version" | ||
| 28 | fi | ||
| 29 | fi | ||
| 30 | |||
| 31 | if [ -d "$src_dir" ] ; then | ||
| 32 | echo "Entering directory \"$src_dir\"..." | ||
| 33 | cd "$src_dir" || exit $? | ||
| 34 | docker build --no-cache -f Dockerfile -t "$tag" . || exit $? | ||
| 35 | else | ||
| 36 | echo "Invalid directory \"$src_dir\"" >&2 | ||
| 37 | exit 30 | ||
| 38 | fi | ||
| 39 | |||
| 40 | if [ -n "$registry" ] ; then | ||
| 41 | #docker login "$registry" || $? | ||
| 42 | docker image push "$tag" | ||
| 43 | fi | ||
| 44 | |||