From 0f1b67611317ab7163a134573b525eb10b415ea8 Mon Sep 17 00:00:00 2001 From: daltux Date: Thu, 4 Sep 2025 15:25:24 -0300 Subject: Add tzdata to container image Install Alpine package `tzdata` to the container image. When running it, set timezone from environment variable TZ if not empty. --- Dockerfile | 3 ++- examples/docker-entrypoint.sh | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index e909cfa..3b5aea2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,8 +9,9 @@ RUN apk -U --no-progress --no-cache add curl-dev build-base && \ cp examples/docker-entrypoint.sh /build/out/usr/local/bin/entrypoint.sh FROM alpine:${ALPINE_VERSION} -RUN apk -U --no-progress --no-cache add libcurl +RUN apk -U --no-progress --no-cache add libcurl tzdata COPY --from=builder /build/out / EXPOSE 5050 VOLUME [ "/data" ] ENTRYPOINT [ "/usr/local/bin/entrypoint.sh" ] + diff --git a/examples/docker-entrypoint.sh b/examples/docker-entrypoint.sh index a6216b2..d198bf2 100755 --- a/examples/docker-entrypoint.sh +++ b/examples/docker-entrypoint.sh @@ -1,7 +1,11 @@ #! /bin/sh -if [ ! -e /data/data/server.json ] -then +if [ -n "$TZ" ] ; then + ln -snf "/usr/share/zoneinfo/$TZ" /etc/localtime \ + && echo "$TZ" > /etc/timezone +fi +if [ ! -e /data/data/server.json ] ; then echo -ne "0.0.0.0\r\n8001\r\nlocalhost\r\n\r\n\r\n" | snac init /data/data snac adduser /data/data testuser fi SSLKEYLOGFILE=/data/key snac httpd /data/data + -- cgit v1.2.3 From ffaa7aeb808c1db3594b5ad1989e20d2154d547c Mon Sep 17 00:00:00 2001 From: daltux Date: Thu, 4 Sep 2025 19:00:34 -0300 Subject: 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. --- examples/docker-swarm-traefik/snac_stack.yml | 114 +++++++++++++++++++++++ examples/docker-swarm-traefik/traefik_config.yml | 89 ++++++++++++++++++ examples/docker_build_snac.sh | 44 +++++++++ 3 files changed, 247 insertions(+) create mode 100644 examples/docker-swarm-traefik/snac_stack.yml create mode 100644 examples/docker-swarm-traefik/traefik_config.yml create mode 100755 examples/docker_build_snac.sh 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 + -- cgit v1.2.3