From 891f3506986f81f764cc0f1c6b434485987912dc Mon Sep 17 00:00:00 2001 From: turegjorup Date: Fri, 7 Nov 2025 13:21:05 +0100 Subject: [PATCH 01/11] Update traefik setup end enable letencrypt acme certs --- .env.traefik.example | 30 ++++++++++++++++ docker-compose.traefik.yml | 11 +++++- traefik/dynamic-conf.yaml | 65 ++++++++++++++++++++++++++++++++-- traefik/letsencrypt/.gitignore | 4 +++ traefik/traefik.yml | 37 ++++++++++++++++--- 5 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 .env.traefik.example create mode 100644 traefik/letsencrypt/.gitignore diff --git a/.env.traefik.example b/.env.traefik.example new file mode 100644 index 0000000..36abcb5 --- /dev/null +++ b/.env.traefik.example @@ -0,0 +1,30 @@ +# Traefik Configuration +# Copy this file to .env.traefik and configure according to your needs + +# Domain for Traefik dashboard access +# Example: traefik.example.com +TRAEFIK_DOMAIN=traefik.example.com + +# Email for Let's Encrypt notifications +# Example: admin@example.com +TRAEFIK_LETSENCRYPT_EMAIL=admin@example.com + +# Dashboard Basic Authentication +# Generate with: htpasswd -nb admin your_password +# Or use: echo $(htpasswd -nb admin your_password) | sed -e s/\\$/\\$\\$/g +# Example: admin:$$apr1$$xyz123... +TRAEFIK_DASHBOARD_AUTH=admin:$$apr1$$ruca84Hq$$mbjdMZBAG.KWn7vfN/SNK/ + +# ACME (Let's Encrypt) CA Server +# For production (default): https://acme-v02.api.letsencrypt.org/directory +# For staging/testing: https://acme-staging-v02.api.letsencrypt.org/directory +# Staging is recommended for testing to avoid hitting rate limits +TRAEFIK_ACME_CA_SERVER=https://acme-staging-v02.api.letsencrypt.org/directory +# For production +# TRAEFIK_ACME_CA_SERVER=https://acme-v02.api.letsencrypt.org/directory + +# Custom Certificate Files (optional) +# If these files exist in traefik/ssl/, they will be used instead of Let's Encrypt +# Leave as default or comment out if using Let's Encrypt +TRAEFIK_CUSTOM_CERT_FILE=custom.crt +TRAEFIK_CUSTOM_KEY_FILE=custom.key diff --git a/docker-compose.traefik.yml b/docker-compose.traefik.yml index c39fcef..b1417b3 100644 --- a/docker-compose.traefik.yml +++ b/docker-compose.traefik.yml @@ -13,14 +13,23 @@ services: ports: - "80:80" - "443:443" - - "8080:8080" # Dashboard + environment: + - TRAEFIK_DOMAIN=${TRAEFIK_DOMAIN} + - TRAEFIK_LETSENCRYPT_EMAIL=${TRAEFIK_LETSENCRYPT_EMAIL} + - TRAEFIK_DASHBOARD_AUTH=${TRAEFIK_DASHBOARD_AUTH} + - TRAEFIK_ACME_CA_SERVER=${TRAEFIK_ACME_CA_SERVER:-https://acme-v02.api.letsencrypt.org/directory} + - TRAEFIK_CUSTOM_CERT_FILE=${TRAEFIK_CUSTOM_CERT_FILE:-custom.crt} + - TRAEFIK_CUSTOM_KEY_FILE=${TRAEFIK_CUSTOM_KEY_FILE:-custom.key} volumes: - $PWD/traefik/ssl:/certs:ro + - $PWD/traefik/letsencrypt:/letsencrypt - $PWD/traefik/traefik.yml:/traefik.yml:ro - $PWD/traefik/dynamic-conf.yaml:/config/dynamic-conf.yaml:ro networks: - frontend - proxy + labels: + - "traefik.enable=true" socket-proxy: image: itkdev/docker-socket-proxy diff --git a/traefik/dynamic-conf.yaml b/traefik/dynamic-conf.yaml index 63aac3f..537a141 100644 --- a/traefik/dynamic-conf.yaml +++ b/traefik/dynamic-conf.yaml @@ -1,5 +1,64 @@ +http: + # Dashboard router with authentication + routers: + dashboard: + rule: "Host(`${TRAEFIK_DOMAIN}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" + service: api@internal + entryPoints: + - websecure + middlewares: + - dashboard-auth + - security-headers + tls: + certResolver: letsencrypt + + # Middlewares + middlewares: + # Basic authentication for dashboard + dashboard-auth: + basicAuth: + users: + - "${TRAEFIK_DASHBOARD_AUTH}" + + # Security headers + security-headers: + headers: + frameDeny: true + browserXssFilter: true + contentTypeNosniff: true + forceSTSHeader: true + stsIncludeSubdomains: true + stsPreload: true + stsSeconds: 63072000 + customFrameOptionsValue: "SAMEORIGIN" + customResponseHeaders: + X-Robots-Tag: "noindex,nofollow,nosnippet,noarchive,notranslate,noimageindex" + server: "" + + # HTTPS redirect middleware (for services that need it) + https-redirect: + redirectScheme: + scheme: https + permanent: true + tls: - certificates: - - certFile: /certs/docker.crt - keyFile: /certs/docker.key + # Modern TLS configuration + options: + modern: + minVersion: VersionTLS12 + cipherSuites: + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 + - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 + curvePreferences: + - CurveP521 + - CurveP384 + sniStrict: true + # Custom certificates (if provided) + certificates: + - certFile: /certs/${TRAEFIK_CUSTOM_CERT_FILE:-custom.crt} + keyFile: /certs/${TRAEFIK_CUSTOM_KEY_FILE:-custom.key} diff --git a/traefik/letsencrypt/.gitignore b/traefik/letsencrypt/.gitignore new file mode 100644 index 0000000..86d0cb2 --- /dev/null +++ b/traefik/letsencrypt/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/traefik/traefik.yml b/traefik/traefik.yml index 46d3370..aff4224 100644 --- a/traefik/traefik.yml +++ b/traefik/traefik.yml @@ -1,27 +1,56 @@ - api: dashboard: true - insecure: true - debug: true + insecure: false entryPoints: web: address: ":80" + http: + redirections: + entryPoint: + to: websecure + scheme: https + permanent: true + websecure: address: ":443" http: tls: - {} + certResolver: letsencrypt + options: modern@file + http3: {} providers: file: directory: /config + watch: true docker: endpoint: "tcp://socket-proxy:2375" exposedByDefault: false +# Let's Encrypt configuration +certificatesResolvers: + letsencrypt: + acme: + email: ${TRAEFIK_LETSENCRYPT_EMAIL} + storage: /letsencrypt/acme.json + # See .env.traefik.example for more info + caServer: ${TRAEFIK_ACME_CA_SERVER:-https://acme-staging-v02.api.letsencrypt.org/directory} + httpChallenge: + entryPoint: web + # https://doc.traefik.io/traefik/routing/services/#insecureskipverify serversTransport: insecureSkipVerify: true +# Logging +log: + level: INFO + format: json + +accessLog: + format: json + fields: + headers: + defaultMode: drop From d683dca1e68f3729e606a29eb76760a795cb4f0d Mon Sep 17 00:00:00 2001 From: turegjorup Date: Fri, 7 Nov 2025 13:47:11 +0100 Subject: [PATCH 02/11] Add front-end network --- .env.traefik.example | 3 +++ docker-compose.traefik.yml | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.env.traefik.example b/.env.traefik.example index 36abcb5..1e55a40 100644 --- a/.env.traefik.example +++ b/.env.traefik.example @@ -23,6 +23,9 @@ TRAEFIK_ACME_CA_SERVER=https://acme-staging-v02.api.letsencrypt.org/directory # For production # TRAEFIK_ACME_CA_SERVER=https://acme-v02.api.letsencrypt.org/directory +# If you use an external proxy you can configure the docker network name here +TRAEFIK_FRONTEND_NETWORK=frontend + # Custom Certificate Files (optional) # If these files exist in traefik/ssl/, they will be used instead of Let's Encrypt # Leave as default or comment out if using Let's Encrypt diff --git a/docker-compose.traefik.yml b/docker-compose.traefik.yml index b1417b3..37771fd 100644 --- a/docker-compose.traefik.yml +++ b/docker-compose.traefik.yml @@ -1,4 +1,6 @@ networks: + frontend: + external: true proxy: driver: bridge internal: true @@ -26,7 +28,7 @@ services: - $PWD/traefik/traefik.yml:/traefik.yml:ro - $PWD/traefik/dynamic-conf.yaml:/config/dynamic-conf.yaml:ro networks: - - frontend + - ${TRAEFIK_FRONTEND_NETWORK:-frontend} - proxy labels: - "traefik.enable=true" From 1f67af71547d00639128c61211520de6223b930c Mon Sep 17 00:00:00 2001 From: turegjorup Date: Fri, 7 Nov 2025 13:49:41 +0100 Subject: [PATCH 03/11] Fix treafik version --- docker-compose.traefik.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.traefik.yml b/docker-compose.traefik.yml index 37771fd..23fae2f 100644 --- a/docker-compose.traefik.yml +++ b/docker-compose.traefik.yml @@ -7,7 +7,7 @@ networks: services: traefik: - image: traefik:v3.2 + image: traefik:v3.5 container_name: traefik restart: unless-stopped security_opt: From 0f9e2f93687496f782edfaa0872818f6bfc7ecc3 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Fri, 7 Nov 2025 14:26:32 +0100 Subject: [PATCH 04/11] Refactor dynamic conf to compose to fix env sustitution --- docker-compose.traefik.yml | 8 ++++++++ traefik/dynamic-conf.yaml | 26 +++++--------------------- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/docker-compose.traefik.yml b/docker-compose.traefik.yml index 23fae2f..4125328 100644 --- a/docker-compose.traefik.yml +++ b/docker-compose.traefik.yml @@ -32,6 +32,14 @@ services: - proxy labels: - "traefik.enable=true" + # Dashboard authentication + - "traefik.http.middlewares.dashboard-auth.basicauth.users=${TRAEFIK_DASHBOARD_AUTH}" + # Dashboard router + - "traefik.http.routers.dashboard.rule=Host(`${TRAEFIK_DOMAIN}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" + - "traefik.http.routers.dashboard.entrypoints=websecure" + - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt" + - "traefik.http.routers.dashboard.service=api@internal" + - "traefik.http.routers.dashboard.middlewares=dashboard-auth,security-headers@file" socket-proxy: image: itkdev/docker-socket-proxy diff --git a/traefik/dynamic-conf.yaml b/traefik/dynamic-conf.yaml index 537a141..c87fc21 100644 --- a/traefik/dynamic-conf.yaml +++ b/traefik/dynamic-conf.yaml @@ -1,25 +1,6 @@ http: - # Dashboard router with authentication - routers: - dashboard: - rule: "Host(`${TRAEFIK_DOMAIN}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" - service: api@internal - entryPoints: - - websecure - middlewares: - - dashboard-auth - - security-headers - tls: - certResolver: letsencrypt - # Middlewares middlewares: - # Basic authentication for dashboard - dashboard-auth: - basicAuth: - users: - - "${TRAEFIK_DASHBOARD_AUTH}" - # Security headers security-headers: headers: @@ -59,6 +40,9 @@ tls: sniStrict: true # Custom certificates (if provided) + # Place your custom certificate files in traefik/ssl/ directory + # Traefik will only use these if the files exist + # If using Let's Encrypt, you can ignore this section or remove the files certificates: - - certFile: /certs/${TRAEFIK_CUSTOM_CERT_FILE:-custom.crt} - keyFile: /certs/${TRAEFIK_CUSTOM_KEY_FILE:-custom.key} + - certFile: /certs/custom.crt + keyFile: /certs/custom.key From c0953908201f2aa4dec3c778ca951fc2a48be122 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Fri, 7 Nov 2025 14:51:59 +0100 Subject: [PATCH 05/11] Update traefik.yml to not have default value for ca server --- traefik/traefik.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/traefik/traefik.yml b/traefik/traefik.yml index aff4224..7b3e71c 100644 --- a/traefik/traefik.yml +++ b/traefik/traefik.yml @@ -34,8 +34,8 @@ certificatesResolvers: acme: email: ${TRAEFIK_LETSENCRYPT_EMAIL} storage: /letsencrypt/acme.json - # See .env.traefik.example for more info - caServer: ${TRAEFIK_ACME_CA_SERVER:-https://acme-staging-v02.api.letsencrypt.org/directory} + # See .env.traefik.example for more info and stg/prod values + caServer: ${TRAEFIK_ACME_CA_SERVER} httpChallenge: entryPoint: web From ca201dabdd24c6df25bdec790aaebb2a9f3d4092 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 12 Nov 2025 13:04:18 +0100 Subject: [PATCH 06/11] Refactor conf --- .env.traefik.example | 4 ++-- .gitignore | 1 + docker-compose.traefik.yml | 7 +++++-- traefik/dynamic-conf.yaml | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.env.traefik.example b/.env.traefik.example index 1e55a40..b5dcb00 100644 --- a/.env.traefik.example +++ b/.env.traefik.example @@ -29,5 +29,5 @@ TRAEFIK_FRONTEND_NETWORK=frontend # Custom Certificate Files (optional) # If these files exist in traefik/ssl/, they will be used instead of Let's Encrypt # Leave as default or comment out if using Let's Encrypt -TRAEFIK_CUSTOM_CERT_FILE=custom.crt -TRAEFIK_CUSTOM_KEY_FILE=custom.key +TRAEFIK_CUSTOM_CERT_FILE=docker.crt +TRAEFIK_CUSTOM_KEY_FILE=docker.key diff --git a/.gitignore b/.gitignore index 3f5a37d..4b165c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .env.local .env.docker.local +.env.traefik \ No newline at end of file diff --git a/docker-compose.traefik.yml b/docker-compose.traefik.yml index 4125328..b0794fc 100644 --- a/docker-compose.traefik.yml +++ b/docker-compose.traefik.yml @@ -12,6 +12,9 @@ services: restart: unless-stopped security_opt: - no-new-privileges:true + env_file: + - path: .env.traefik + required: true ports: - "80:80" - "443:443" @@ -20,8 +23,8 @@ services: - TRAEFIK_LETSENCRYPT_EMAIL=${TRAEFIK_LETSENCRYPT_EMAIL} - TRAEFIK_DASHBOARD_AUTH=${TRAEFIK_DASHBOARD_AUTH} - TRAEFIK_ACME_CA_SERVER=${TRAEFIK_ACME_CA_SERVER:-https://acme-v02.api.letsencrypt.org/directory} - - TRAEFIK_CUSTOM_CERT_FILE=${TRAEFIK_CUSTOM_CERT_FILE:-custom.crt} - - TRAEFIK_CUSTOM_KEY_FILE=${TRAEFIK_CUSTOM_KEY_FILE:-custom.key} + - TRAEFIK_CUSTOM_CERT_FILE=${TRAEFIK_CUSTOM_CERT_FILE:-docker.crt} + - TRAEFIK_CUSTOM_KEY_FILE=${TRAEFIK_CUSTOM_KEY_FILE:-docker.key} volumes: - $PWD/traefik/ssl:/certs:ro - $PWD/traefik/letsencrypt:/letsencrypt diff --git a/traefik/dynamic-conf.yaml b/traefik/dynamic-conf.yaml index c87fc21..6e7dafa 100644 --- a/traefik/dynamic-conf.yaml +++ b/traefik/dynamic-conf.yaml @@ -44,5 +44,5 @@ tls: # Traefik will only use these if the files exist # If using Let's Encrypt, you can ignore this section or remove the files certificates: - - certFile: /certs/custom.crt - keyFile: /certs/custom.key + - certFile: /certs/{{ env "TRAEFIK_CUSTOM_CERT_FILE" }} + keyFile: /certs/{{ env "TRAEFIK_CUSTOM_KEY_FILE" }} From 429be0b378d82b2886b8785a044bbef5db60bedd Mon Sep 17 00:00:00 2001 From: turegjorup Date: Fri, 14 Nov 2025 10:15:54 +0100 Subject: [PATCH 07/11] Refactor traefik setup for both local and letsencrypt certificates --- .env.traefik.example | 33 +++++++++++------ docker-compose.traefik.yml | 11 ++---- ...-conf.yaml => dynamic-conf-cert-file.yaml} | 8 ++--- traefik/dynamic-conf-letsencrypt.yaml | 36 +++++++++++++++++++ traefik/traefik.yml | 4 +-- 5 files changed, 64 insertions(+), 28 deletions(-) rename traefik/{dynamic-conf.yaml => dynamic-conf-cert-file.yaml} (83%) create mode 100644 traefik/dynamic-conf-letsencrypt.yaml diff --git a/.env.traefik.example b/.env.traefik.example index b5dcb00..7c55c2f 100644 --- a/.env.traefik.example +++ b/.env.traefik.example @@ -1,20 +1,32 @@ # Traefik Configuration # Copy this file to .env.traefik and configure according to your needs +# If you use an external proxy you can configure the docker network name here +TRAEFIK_FRONTEND_NETWORK=frontend + # Domain for Traefik dashboard access # Example: traefik.example.com TRAEFIK_DOMAIN=traefik.example.com -# Email for Let's Encrypt notifications -# Example: admin@example.com -TRAEFIK_LETSENCRYPT_EMAIL=admin@example.com - # Dashboard Basic Authentication # Generate with: htpasswd -nb admin your_password # Or use: echo $(htpasswd -nb admin your_password) | sed -e s/\\$/\\$\\$/g # Example: admin:$$apr1$$xyz123... TRAEFIK_DASHBOARD_AUTH=admin:$$apr1$$ruca84Hq$$mbjdMZBAG.KWn7vfN/SNK/ + +######### HTTPS/SSL setup ######### +# This setup can use either Let's Encrypt (default) or local cert/key fiels. This is controlled by setting +# TRAEFIK_CERT_PROVIDER to either "letsencrypt" or "cert-file" + +##### Let's Encrypt ##### +# By default this setup uses Let's Encrypt for https certificates. Alternatively you can provide a local certificate. +TRAEFIK_CERT_PROVIDER=letsencrypt + +# Email for Let's Encrypt notifications +# Example: admin@example.com +TRAEFIK_LETSENCRYPT_EMAIL=admin@example.com + # ACME (Let's Encrypt) CA Server # For production (default): https://acme-v02.api.letsencrypt.org/directory # For staging/testing: https://acme-staging-v02.api.letsencrypt.org/directory @@ -23,11 +35,10 @@ TRAEFIK_ACME_CA_SERVER=https://acme-staging-v02.api.letsencrypt.org/directory # For production # TRAEFIK_ACME_CA_SERVER=https://acme-v02.api.letsencrypt.org/directory -# If you use an external proxy you can configure the docker network name here -TRAEFIK_FRONTEND_NETWORK=frontend -# Custom Certificate Files (optional) -# If these files exist in traefik/ssl/, they will be used instead of Let's Encrypt -# Leave as default or comment out if using Let's Encrypt -TRAEFIK_CUSTOM_CERT_FILE=docker.crt -TRAEFIK_CUSTOM_KEY_FILE=docker.key +##### Local/custom certificae ##### +# TRAEFIK_CERT_PROVIDER=cert-file +# Palce a valid SSL certificate and private key for the domain in the traefik/ssl` directory. +# Then set the following to match the filenames for the provided files. +# TRAEFIK_CUSTOM_CERT_FILE=docker.crt +# TRAEFIK_CUSTOM_KEY_FILE=docker.key diff --git a/docker-compose.traefik.yml b/docker-compose.traefik.yml index b0794fc..8d43d2e 100644 --- a/docker-compose.traefik.yml +++ b/docker-compose.traefik.yml @@ -7,7 +7,7 @@ networks: services: traefik: - image: traefik:v3.5 + image: traefik:v3.6 container_name: traefik restart: unless-stopped security_opt: @@ -18,18 +18,11 @@ services: ports: - "80:80" - "443:443" - environment: - - TRAEFIK_DOMAIN=${TRAEFIK_DOMAIN} - - TRAEFIK_LETSENCRYPT_EMAIL=${TRAEFIK_LETSENCRYPT_EMAIL} - - TRAEFIK_DASHBOARD_AUTH=${TRAEFIK_DASHBOARD_AUTH} - - TRAEFIK_ACME_CA_SERVER=${TRAEFIK_ACME_CA_SERVER:-https://acme-v02.api.letsencrypt.org/directory} - - TRAEFIK_CUSTOM_CERT_FILE=${TRAEFIK_CUSTOM_CERT_FILE:-docker.crt} - - TRAEFIK_CUSTOM_KEY_FILE=${TRAEFIK_CUSTOM_KEY_FILE:-docker.key} volumes: - $PWD/traefik/ssl:/certs:ro - $PWD/traefik/letsencrypt:/letsencrypt - $PWD/traefik/traefik.yml:/traefik.yml:ro - - $PWD/traefik/dynamic-conf.yaml:/config/dynamic-conf.yaml:ro + - $PWD/traefik/dynamic-conf-${TRAEFIK_CERT_PROVIDER:-letsencrypt}.yaml:/config/dynamic-conf.yaml:ro networks: - ${TRAEFIK_FRONTEND_NETWORK:-frontend} - proxy diff --git a/traefik/dynamic-conf.yaml b/traefik/dynamic-conf-cert-file.yaml similarity index 83% rename from traefik/dynamic-conf.yaml rename to traefik/dynamic-conf-cert-file.yaml index 6e7dafa..2a4d16f 100644 --- a/traefik/dynamic-conf.yaml +++ b/traefik/dynamic-conf-cert-file.yaml @@ -1,7 +1,5 @@ http: - # Middlewares middlewares: - # Security headers security-headers: headers: frameDeny: true @@ -16,14 +14,12 @@ http: X-Robots-Tag: "noindex,nofollow,nosnippet,noarchive,notranslate,noimageindex" server: "" - # HTTPS redirect middleware (for services that need it) https-redirect: redirectScheme: scheme: https permanent: true tls: - # Modern TLS configuration options: modern: minVersion: VersionTLS12 @@ -44,5 +40,5 @@ tls: # Traefik will only use these if the files exist # If using Let's Encrypt, you can ignore this section or remove the files certificates: - - certFile: /certs/{{ env "TRAEFIK_CUSTOM_CERT_FILE" }} - keyFile: /certs/{{ env "TRAEFIK_CUSTOM_KEY_FILE" }} + - certFile: "/certs/{{ env \"TRAEFIK_CUSTOM_CERT_FILE\" }}" + keyFile: "/certs/{{ env \"TRAEFIK_CUSTOM_KEY_FILE\" }}" \ No newline at end of file diff --git a/traefik/dynamic-conf-letsencrypt.yaml b/traefik/dynamic-conf-letsencrypt.yaml new file mode 100644 index 0000000..2cec5c5 --- /dev/null +++ b/traefik/dynamic-conf-letsencrypt.yaml @@ -0,0 +1,36 @@ +http: + middlewares: + security-headers: + headers: + frameDeny: true + browserXssFilter: true + contentTypeNosniff: true + forceSTSHeader: true + stsIncludeSubdomains: true + stsPreload: true + stsSeconds: 63072000 + customFrameOptionsValue: "SAMEORIGIN" + customResponseHeaders: + X-Robots-Tag: "noindex,nofollow,nosnippet,noarchive,notranslate,noimageindex" + server: "" + + https-redirect: + redirectScheme: + scheme: https + permanent: true + +tls: + options: + modern: + minVersion: VersionTLS12 + cipherSuites: + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 + - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 + curvePreferences: + - CurveP521 + - CurveP384 + sniStrict: true diff --git a/traefik/traefik.yml b/traefik/traefik.yml index 7b3e71c..6c1a159 100644 --- a/traefik/traefik.yml +++ b/traefik/traefik.yml @@ -32,10 +32,10 @@ providers: certificatesResolvers: letsencrypt: acme: - email: ${TRAEFIK_LETSENCRYPT_EMAIL} + email: '{{ env "TRAEFIK_LETSENCRYPT_EMAIL" }}' storage: /letsencrypt/acme.json # See .env.traefik.example for more info and stg/prod values - caServer: ${TRAEFIK_ACME_CA_SERVER} + caServer: '{{ env "TRAEFIK_ACME_CA_SERVER" }}' httpChallenge: entryPoint: web From ca62b727279e0a190e2760689c323177065bf5b0 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Mon, 17 Nov 2025 11:30:42 +0100 Subject: [PATCH 08/11] Separate SERVER from TRAEFIK env values --- .env.traefik.example | 30 ++++++++++++++++------------- docker-compose.traefik.yml | 8 ++++---- traefik/dynamic-conf-cert-file.yaml | 4 ++-- traefik/traefik.yml | 6 +++--- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/.env.traefik.example b/.env.traefik.example index 7c55c2f..7fe9493 100644 --- a/.env.traefik.example +++ b/.env.traefik.example @@ -1,44 +1,48 @@ # Traefik Configuration # Copy this file to .env.traefik and configure according to your needs +# +# SERVER_* values are application/setup specific +# TRAEFIK_* values are Traefik configuration values, +# @see https://doc.traefik.io/traefik/reference/install-configuration/boot-environment/#environment-variables # If you use an external proxy you can configure the docker network name here -TRAEFIK_FRONTEND_NETWORK=frontend +SERVER_FRONTEND_NETWORK=frontend # Domain for Traefik dashboard access # Example: traefik.example.com -TRAEFIK_DOMAIN=traefik.example.com +SERVER_DOMAIN=traefik.example.com # Dashboard Basic Authentication -# Generate with: htpasswd -nb admin your_password -# Or use: echo $(htpasswd -nb admin your_password) | sed -e s/\\$/\\$\\$/g +# Generate with: htpasswd -n admin +# Or use: echo $(htpasswd -n admin) | sed -e s/\\$/\\$\\$/g # Example: admin:$$apr1$$xyz123... -TRAEFIK_DASHBOARD_AUTH=admin:$$apr1$$ruca84Hq$$mbjdMZBAG.KWn7vfN/SNK/ +SERVER_DASHBOARD_AUTH=admin:$$apr1$$ruca84Hq$$mbjdMZBAG.KWn7vfN/SNK/ ######### HTTPS/SSL setup ######### # This setup can use either Let's Encrypt (default) or local cert/key fiels. This is controlled by setting -# TRAEFIK_CERT_PROVIDER to either "letsencrypt" or "cert-file" +# SERVER_CERT_PROVIDER to either "letsencrypt" or "cert-file" ##### Let's Encrypt ##### # By default this setup uses Let's Encrypt for https certificates. Alternatively you can provide a local certificate. -TRAEFIK_CERT_PROVIDER=letsencrypt +SERVER_CERT_PROVIDER=letsencrypt # Email for Let's Encrypt notifications # Example: admin@example.com -TRAEFIK_LETSENCRYPT_EMAIL=admin@example.com +TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL=admin@example.com # ACME (Let's Encrypt) CA Server # For production (default): https://acme-v02.api.letsencrypt.org/directory # For staging/testing: https://acme-staging-v02.api.letsencrypt.org/directory # Staging is recommended for testing to avoid hitting rate limits -TRAEFIK_ACME_CA_SERVER=https://acme-staging-v02.api.letsencrypt.org/directory +TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_CASERVER=https://acme-staging-v02.api.letsencrypt.org/directory # For production -# TRAEFIK_ACME_CA_SERVER=https://acme-v02.api.letsencrypt.org/directory +# TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_CASERVER=https://acme-v02.api.letsencrypt.org/directory ##### Local/custom certificae ##### -# TRAEFIK_CERT_PROVIDER=cert-file +# SERVER_CERT_PROVIDER=cert-file # Palce a valid SSL certificate and private key for the domain in the traefik/ssl` directory. # Then set the following to match the filenames for the provided files. -# TRAEFIK_CUSTOM_CERT_FILE=docker.crt -# TRAEFIK_CUSTOM_KEY_FILE=docker.key +# SERVER_CUSTOM_CERT_FILE=docker.crt +# SERVER_CUSTOM_KEY_FILE=docker.key diff --git a/docker-compose.traefik.yml b/docker-compose.traefik.yml index 8d43d2e..336b71e 100644 --- a/docker-compose.traefik.yml +++ b/docker-compose.traefik.yml @@ -22,16 +22,16 @@ services: - $PWD/traefik/ssl:/certs:ro - $PWD/traefik/letsencrypt:/letsencrypt - $PWD/traefik/traefik.yml:/traefik.yml:ro - - $PWD/traefik/dynamic-conf-${TRAEFIK_CERT_PROVIDER:-letsencrypt}.yaml:/config/dynamic-conf.yaml:ro + - $PWD/traefik/dynamic-conf-${SERVER_CERT_PROVIDER:-letsencrypt}.yaml:/config/dynamic-conf.yaml:ro networks: - - ${TRAEFIK_FRONTEND_NETWORK:-frontend} + - ${SERVER_FRONTEND_NETWORK:-frontend} - proxy labels: - "traefik.enable=true" # Dashboard authentication - - "traefik.http.middlewares.dashboard-auth.basicauth.users=${TRAEFIK_DASHBOARD_AUTH}" + - "traefik.http.middlewares.dashboard-auth.basicauth.users=${SERVER_DASHBOARD_AUTH}" # Dashboard router - - "traefik.http.routers.dashboard.rule=Host(`${TRAEFIK_DOMAIN}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" + - "traefik.http.routers.dashboard.rule=Host(`${SERVER_DOMAIN}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" - "traefik.http.routers.dashboard.entrypoints=websecure" - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt" - "traefik.http.routers.dashboard.service=api@internal" diff --git a/traefik/dynamic-conf-cert-file.yaml b/traefik/dynamic-conf-cert-file.yaml index 2a4d16f..1c519e2 100644 --- a/traefik/dynamic-conf-cert-file.yaml +++ b/traefik/dynamic-conf-cert-file.yaml @@ -40,5 +40,5 @@ tls: # Traefik will only use these if the files exist # If using Let's Encrypt, you can ignore this section or remove the files certificates: - - certFile: "/certs/{{ env \"TRAEFIK_CUSTOM_CERT_FILE\" }}" - keyFile: "/certs/{{ env \"TRAEFIK_CUSTOM_KEY_FILE\" }}" \ No newline at end of file + - certFile: "/certs/{{ env \"SERVER_CUSTOM_CERT_FILE\" }}" + keyFile: "/certs/{{ env \"SERVER_CUSTOM_KEY_FILE\" }}" diff --git a/traefik/traefik.yml b/traefik/traefik.yml index 6c1a159..cd4190d 100644 --- a/traefik/traefik.yml +++ b/traefik/traefik.yml @@ -32,10 +32,10 @@ providers: certificatesResolvers: letsencrypt: acme: - email: '{{ env "TRAEFIK_LETSENCRYPT_EMAIL" }}' - storage: /letsencrypt/acme.json # See .env.traefik.example for more info and stg/prod values - caServer: '{{ env "TRAEFIK_ACME_CA_SERVER" }}' + # email: + # caServer: + storage: /letsencrypt/acme.json httpChallenge: entryPoint: web From 2eae377b4028806cf7106089e03a6dce04b8db60 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Mon, 17 Nov 2025 14:42:27 +0100 Subject: [PATCH 09/11] Add trafik env task to set variables --- .env.traefik.example | 6 ++--- .gitignore | 2 +- Taskfile.yml | 46 ++++++++++++++++++++++++++++++++++ traefik/letsencrypt/.gitignore | 2 +- 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/.env.traefik.example b/.env.traefik.example index 7fe9493..cdb92cc 100644 --- a/.env.traefik.example +++ b/.env.traefik.example @@ -16,7 +16,7 @@ SERVER_DOMAIN=traefik.example.com # Generate with: htpasswd -n admin # Or use: echo $(htpasswd -n admin) | sed -e s/\\$/\\$\\$/g # Example: admin:$$apr1$$xyz123... -SERVER_DASHBOARD_AUTH=admin:$$apr1$$ruca84Hq$$mbjdMZBAG.KWn7vfN/SNK/ +SERVER_DASHBOARD_AUTH=admin:password ######### HTTPS/SSL setup ######### @@ -35,9 +35,9 @@ TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL=admin@example.com # For production (default): https://acme-v02.api.letsencrypt.org/directory # For staging/testing: https://acme-staging-v02.api.letsencrypt.org/directory # Staging is recommended for testing to avoid hitting rate limits -TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_CASERVER=https://acme-staging-v02.api.letsencrypt.org/directory +# TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_CASERVER=https://acme-staging-v02.api.letsencrypt.org/directory # For production -# TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_CASERVER=https://acme-v02.api.letsencrypt.org/directory +TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_CASERVER=https://acme-v02.api.letsencrypt.org/directory ##### Local/custom certificae ##### diff --git a/.gitignore b/.gitignore index 4b165c3..124576d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .env.local .env.docker.local -.env.traefik \ No newline at end of file +.env.traefik diff --git a/Taskfile.yml b/Taskfile.yml index 43fe228..d2c089d 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -51,6 +51,47 @@ tasks: - task load_templates - task _show_notes + traefik_env: + desc: Setup .env.traefik (domain, email, dashboard auth) + cmds: + - | + if [ ! -f .env.traefik ]; then + echo ".env.traefik does not exist. Copying .env.traefik.example to .env.traefik..." + cp .env.traefik.example .env.traefik + fi + echo "" + echo "Configure Traefik environment" + echo "====================================================" + printf "Enter server domain (e.g. example.com): " + read SERVER_DOMAIN + printf "Enter email for Let's Encrypt (e.g. admin@example.com): " + read TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL + printf "Enter Traefik dashboard/admin username: " + read SERVER_DASHBOARD_USERNAME + printf "Enter Traefik dashboard/admin password: " + read -s SERVER_DASHBOARD_PASSWORD + + # Generate htpasswd entry (username:hash) + HTPASSWD_RAW=$(htpasswd -nb "${SERVER_DASHBOARD_USERNAME}" "${SERVER_DASHBOARD_PASSWORD}") + + # Escape characters that break sed/env ($, /, &) + HTPASSWD_ESCAPED=$(printf '%s\n' "$HTPASSWD_RAW" \ + | sed -e 's/[\/&]/\\&/g' -e 's/\$/\\$/g') + + # Update variables in .env.traefik + sed -i "s/^SERVER_DOMAIN=.*/SERVER_DOMAIN=${SERVER_DOMAIN}/" .env.traefik + sed -i "s/^TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL=.*/TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL=${TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL}/" .env.traefik + sed -i "s/^SERVER_DASHBOARD_AUTH=.*/SERVER_DASHBOARD_AUTH=${HTPASSWD_ESCAPED}/" .env.traefik + + echo "====================================================" + echo ".env.traefik has been updated." + echo "Domain: ${SERVER_DOMAIN}" + echo "Email: ${TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL}" + echo "User: ${SERVER_DASHBOARD_USERNAME}" + echo "Password: (hidden)" + echo "Auth: (htpasswd line stored in SERVER_DASHBOARD_AUTH)" + echo "====================================================" + reinstall: desc: Reinstall from scratch. Removes the database, all containers, and volumes. deps: @@ -208,6 +249,11 @@ tasks: echo ".env.docker.local does not exist. Copying .env.docker.example to .env.docker.local..." cp .env.docker.example .env.docker.local fi + - | + if [ ! -f .env.traefik ]; then + echo ".env.traefik does not exist. Copying .env.traefik.example to .env.traefik..." + cp .env.traefik.example .env.traefik + fi _dc_compile: deps: diff --git a/traefik/letsencrypt/.gitignore b/traefik/letsencrypt/.gitignore index 86d0cb2..5e7d273 100644 --- a/traefik/letsencrypt/.gitignore +++ b/traefik/letsencrypt/.gitignore @@ -1,4 +1,4 @@ # Ignore everything in this directory * # Except this file -!.gitignore \ No newline at end of file +!.gitignore From f2a47103e2db554047bfc3cc2cc047ddf5df6fd3 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Mon, 17 Nov 2025 14:57:17 +0100 Subject: [PATCH 10/11] Update readme with Let's Encrypt instructions --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3175434..4216c55 100644 --- a/README.md +++ b/README.md @@ -43,13 +43,18 @@ sudo passwd deploy sudo usermod -aG docker deploy ``` -## Secure Mode Requirement +## Traefik Configuration -This project can only run in secure mode using HTTPS (port 443). To ensure proper functionality, you must provide a valid domain name and an SSL certificate. +### Secure Mode Requirement + +This project can only run in secure mode using HTTPS (port 443). A Traefik reverse proxy will handle HTTPS using either +* Let's encrypt certificates (default) +* Custom certificate/key files ### Steps to Configure Secure Mode: 1. **Domain Name**: Use a fully qualified domain name (FQDN) that resolves to your server's IP address. -2. **SSL Certificate**: Provide a valid SSL certificate and private key for the domain. +2. **SSL Certificate**, either: + - Let traefik generate a certificate using Let's Encrypt (default). - Place the certificate file (`docker.crt`) and the private key file (`docker.key`) in the `traefik/ssl` directory. 3. **Update Configuration**: Ensure the domain name is correctly configured in the `.env.docker.local` file. @@ -60,6 +65,7 @@ Without a valid domain name and SSL certificate, the project will not function a The project uses a `Taskfile.yml` to simplify common operations. Below is a list of the most important tasks you can run: ### Installation and Setup +- **`task traefik_env`**: Configures Traefik to use Let's Encrypt certificates or custom certificates. - **`task install`**: Installs the project, pulls Docker images, sets up the database, and initializes the environment. - **`task reinstall`**: Reinstalls the project from scratch, removing all containers, volumes, and the database. - **`task up`**: Starts the environment without altering the existing state of the containers. From 1740ee22a1da63dfe35cb3b5e09e1c5a07e65e82 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Mon, 17 Nov 2025 15:10:59 +0100 Subject: [PATCH 11/11] Add check and help text for htpasswd --- Taskfile.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Taskfile.yml b/Taskfile.yml index d2c089d..4179996 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -59,7 +59,16 @@ tasks: echo ".env.traefik does not exist. Copying .env.traefik.example to .env.traefik..." cp .env.traefik.example .env.traefik fi + + # Ensure htpasswd is installed + if ! command -v htpasswd >/dev/null 2>&1; then + echo "Error: 'htpasswd' command not found." + echo "Please install it (usually from the 'apache2-utils' or 'httpd-tools' package) and try again." + exit 1 + fi + echo "" + echo "Configure Traefik environment" echo "====================================================" printf "Enter server domain (e.g. example.com): "