From 7599c819f74ec2fb176ed6304358dbde40115877 Mon Sep 17 00:00:00 2001 From: binilsn Date: Mon, 15 Jun 2026 13:51:25 +0530 Subject: [PATCH 1/2] feat: add Traefik reverse proxy with wildcard SSL via Let's Encrypt - Traefik v3 with DNS challenge via Cloudflare - Automatic HTTP to HTTPS redirect - Wildcard certificate support - Local docker run path preserved for development --- .gitignore | 3 +++ README.md | 19 ++++++++++++++++++- config/traefik/dynamic.yml | 20 ++++++++++++++++++++ config/traefik/traefik.yml | 33 +++++++++++++++++++++++++++++++++ docker-compose.yml | 27 +++++++++++++++++++++++++-- 5 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 config/traefik/dynamic.yml create mode 100644 config/traefik/traefik.yml diff --git a/.gitignore b/.gitignore index 64f8ef0..57ab061 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,9 @@ # Ignore key files for decrypting credentials and more. /config/*.key +# Ignore Traefik certificate storage +acme.json + /app/assets/builds/* !/app/assets/builds/.keep diff --git a/README.md b/README.md index 3fdebb5..ce7e66b 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,24 @@ bin/dev ## Docker -### One-command deploy +### Production with SSL + +Use Docker Compose with Traefik for automatic HTTPS via Let's Encrypt: + +```bash +# Set required env vars +export DOMAIN=uptime.example.com +export WILDCARD_DOMAIN=*.example.com +export LETSENCRYPT_EMAIL=you@example.com +export CF_DNS_API_TOKEN=your-cloudflare-token +export ADMIN_EMAILS=admin@example.com + +docker compose up -d +``` + +Requires a Cloudflare API token with `Zone:DNS:Edit` permission for wildcard certificate DNS challenge. + +### One-command deploy (local) ```bash docker run -d -p 3000:80 \ diff --git a/config/traefik/dynamic.yml b/config/traefik/dynamic.yml new file mode 100644 index 0000000..aac794b --- /dev/null +++ b/config/traefik/dynamic.yml @@ -0,0 +1,20 @@ +# Dynamic configuration +http: + routers: + uptimer: + rule: "Host(`{{ env \"DOMAIN\" }}`)" + entryPoints: + - websecure + service: uptimer + tls: + certResolver: letsencrypt + domains: + - main: "{{ env \"DOMAIN\" }}" + sans: + - "{{ env \"WILDCARD_DOMAIN\" }}" + + services: + uptimer: + loadBalancer: + servers: + - url: "http://up-timer:80" diff --git a/config/traefik/traefik.yml b/config/traefik/traefik.yml new file mode 100644 index 0000000..cc167f7 --- /dev/null +++ b/config/traefik/traefik.yml @@ -0,0 +1,33 @@ +# Static configuration +global: + sendAnonymousUsage: false + +api: + dashboard: false + +entryPoints: + web: + address: ":80" + http: + redirections: + entryPoint: + to: websecure + scheme: https + websecure: + address: ":443" + +providers: + file: + filename: /etc/traefik/dynamic.yml + watch: true + +certificatesResolvers: + letsencrypt: + acme: + email: "{{ env \"LETSENCRYPT_EMAIL\" }}" + storage: /letsencrypt/acme.json + dnsChallenge: + provider: cloudflare + resolvers: + - "1.1.1.1:53" + - "8.8.8.8:53" diff --git a/docker-compose.yml b/docker-compose.yml index bcdeaaa..393dca7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,30 @@ services: + traefik: + image: traefik:v3 + container_name: traefik + ports: + - "80:80" + - "443:443" + volumes: + - ./config/traefik/traefik.yml:/etc/traefik/traefik.yml:ro + - ./config/traefik/dynamic.yml:/etc/traefik/dynamic.yml:ro + - traefik-certs:/letsencrypt + environment: + - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN} + - LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL} + - DOMAIN=${DOMAIN} + - WILDCARD_DOMAIN=${WILDCARD_DOMAIN} + restart: unless-stopped + up-timer: image: binilsn/up-timer:latest build: . - ports: - - "3000:80" environment: - RAILS_ENV=production - RAILS_MASTER_KEY=${RAILS_MASTER_KEY} - ADMIN_EMAILS=${ADMIN_EMAILS} - SOLID_QUEUE_IN_PUMA=true + - APP_HOST=${DOMAIN} volumes: - up-timer-storage:/rails/storage - up-timer-db:/rails/db @@ -17,7 +33,14 @@ services: interval: 10s timeout: 3s retries: 3 + labels: + - "traefik.enable=true" + - "traefik.http.routers.uptimer.rule=Host(`${DOMAIN}`)" + - "traefik.http.routers.uptimer.entrypoints=websecure" + - "traefik.http.routers.uptimer.tls.certresolver=letsencrypt" + restart: unless-stopped volumes: up-timer-storage: up-timer-db: + traefik-certs: From 50e03210202597fda547a6d2ae82d9149bbfb0d3 Mon Sep 17 00:00:00 2001 From: binilsn Date: Mon, 15 Jun 2026 14:11:20 +0530 Subject: [PATCH 2/2] docs: show comma-separated admin emails, restore Cloudflare section --- README.md | 32 +++++++++++++++++++------- config/traefik/dynamic.yml | 34 ++++++++++++++-------------- config/traefik/traefik.yml | 46 +++++++++++++++++++------------------- docker-compose.yml | 2 ++ 4 files changed, 66 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index ce7e66b..77ce489 100644 --- a/README.md +++ b/README.md @@ -49,20 +49,36 @@ bin/dev ### Production with SSL -Use Docker Compose with Traefik for automatic HTTPS via Let's Encrypt: +Use Docker Compose with Traefik for HTTPS. Create a `.env` file with SSL and app config: + +**Let's Encrypt (wildcard):** + +```bash +# .env +DOMAIN=uptime.example.com +WILDCARD_DOMAIN=*.example.com +DNS_PROVIDER=cloudflare +CF_DNS_API_TOKEN=your-token +LETSENCRYPT_EMAIL=you@example.com +# App config (see table above) +ADMIN_EMAILS=admin@example.com,manager@example.com + +docker compose up -d +``` + +**Cloudflare SSL (no cert management):** ```bash -# Set required env vars -export DOMAIN=uptime.example.com -export WILDCARD_DOMAIN=*.example.com -export LETSENCRYPT_EMAIL=you@example.com -export CF_DNS_API_TOKEN=your-cloudflare-token -export ADMIN_EMAILS=admin@example.com +# .env +DOMAIN=uptime.example.com +ENTRYPOINT=web +# App config (see table above) +ADMIN_EMAILS=admin@example.com docker compose up -d ``` -Requires a Cloudflare API token with `Zone:DNS:Edit` permission for wildcard certificate DNS challenge. +If `ENTRYPOINT` is not set, Traefik defaults to `websecure` (HTTPS) with automatic Let's Encrypt DNS challenge. [Supported DNS providers](https://doc.traefik.io/traefik/https/acme/#dnschallenge) ### One-command deploy (local) diff --git a/config/traefik/dynamic.yml b/config/traefik/dynamic.yml index aac794b..2d15733 100644 --- a/config/traefik/dynamic.yml +++ b/config/traefik/dynamic.yml @@ -1,20 +1,20 @@ # Dynamic configuration http: - routers: - uptimer: - rule: "Host(`{{ env \"DOMAIN\" }}`)" - entryPoints: - - websecure - service: uptimer - tls: - certResolver: letsencrypt - domains: - - main: "{{ env \"DOMAIN\" }}" - sans: - - "{{ env \"WILDCARD_DOMAIN\" }}" + routers: + uptimer: + rule: 'Host(`{{ env "DOMAIN" }}`)' + entryPoints: + - '{{ env "ENTRYPOINT" "websecure" }}' + service: uptimer + tls: + certResolver: letsencrypt + domains: + - main: '{{ env "DOMAIN" }}' + sans: + - '{{ env "WILDCARD_DOMAIN" }}' - services: - uptimer: - loadBalancer: - servers: - - url: "http://up-timer:80" + services: + uptimer: + loadBalancer: + servers: + - url: "http://up-timer:80" diff --git a/config/traefik/traefik.yml b/config/traefik/traefik.yml index cc167f7..d728497 100644 --- a/config/traefik/traefik.yml +++ b/config/traefik/traefik.yml @@ -1,33 +1,33 @@ # Static configuration global: - sendAnonymousUsage: false + sendAnonymousUsage: false api: - dashboard: false + dashboard: false entryPoints: - web: - address: ":80" - http: - redirections: - entryPoint: - to: websecure - scheme: https - websecure: - address: ":443" + web: + address: ":80" + http: + redirections: + entryPoint: + to: websecure + scheme: https + websecure: + address: ":443" providers: - file: - filename: /etc/traefik/dynamic.yml - watch: true + file: + filename: /etc/traefik/dynamic.yml + watch: true certificatesResolvers: - letsencrypt: - acme: - email: "{{ env \"LETSENCRYPT_EMAIL\" }}" - storage: /letsencrypt/acme.json - dnsChallenge: - provider: cloudflare - resolvers: - - "1.1.1.1:53" - - "8.8.8.8:53" + letsencrypt: + acme: + email: '{{ env "LETSENCRYPT_EMAIL" }}' + storage: /letsencrypt/acme.json + dnsChallenge: + provider: '{{ env "DNS_PROVIDER" "cloudflare" }}' + resolvers: + - "1.1.1.1:53" + - "8.8.8.8:53" diff --git a/docker-compose.yml b/docker-compose.yml index 393dca7..c3f9a54 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,8 @@ services: - ./config/traefik/dynamic.yml:/etc/traefik/dynamic.yml:ro - traefik-certs:/letsencrypt environment: + - ENTRYPOINT=${ENTRYPOINT:-websecure} + - DNS_PROVIDER=${DNS_PROVIDER:-cloudflare} - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN} - LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL} - DOMAIN=${DOMAIN}