diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 95092cdb..cf778808 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -40,6 +40,7 @@ jobs: - name: Build image run: | docker build \ + --pull \ -t registry.infra.mintel.me/mintel/klz-cables.com:latest . # --- Push image --- @@ -63,6 +64,13 @@ jobs: rsync -av ./docker-compose.y*ml \ deploy@alpha.mintel.me:/home/deploy/sites/klz-cables.com/ + # --- Sync varnish config (optional) --- + - name: Sync varnish config + run: | + if [ -d ./varnish ]; then + rsync -av ./varnish/ deploy@alpha.mintel.me:/home/deploy/sites/klz-cables.com/varnish/ + fi + # --- Deploy --- - name: Deploy on server run: | @@ -70,5 +78,6 @@ jobs: cd /home/deploy/sites/klz-cables.com && docker compose -f docker-compose.yml pull 2>/dev/null || docker compose -f docker-compose.yaml pull && + docker image prune -f && docker compose up -d ' diff --git a/docker-compose.yml b/docker-compose.yml index 472d0083..2b82b4f3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,50 @@ services: + # Varnish sits between Traefik and the application. + # + # Flow: + # Client -> Traefik -> Varnish -> app + # + # Traefik keeps TLS + compression; Varnish adds HTTP caching for static assets. + varnish: + image: varnish:7 + restart: always + networks: + - infra + depends_on: + - app + command: >- + varnishd + -F + -f /etc/varnish/default.vcl + -s malloc,${VARNISH_CACHE_SIZE:-256m} + volumes: + - ./varnish/default.vcl:/etc/varnish/default.vcl:ro + healthcheck: + test: ["CMD-SHELL", "wget --quiet --tries=1 --spider http://localhost:80/health || wget --quiet --tries=1 --spider http://localhost:80/ || true"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + labels: + - "traefik.enable=true" + # HTTP → HTTPS redirect (Challenge-Schutz für ALLE) + - "traefik.http.routers.klz-cables-web.rule=(Host(`klz-cables.com`) || Host(`www.klz-cables.com`) || Host(`staging.klz-cables.com`)) && !PathPrefix(`/.well-known/acme-challenge/`)" + - "traefik.http.routers.klz-cables-web.entrypoints=web" + - "traefik.http.routers.klz-cables-web.middlewares=redirect-https" + # HTTPS router (für ALLE drei Domains) + - "traefik.http.routers.klz-cables.rule=Host(`klz-cables.com`) || Host(`www.klz-cables.com`) || Host(`staging.klz-cables.com`)" + - "traefik.http.routers.klz-cables.entrypoints=websecure" + - "traefik.http.routers.klz-cables.tls.certresolver=le" + - "traefik.http.routers.klz-cables.tls=true" + - "traefik.http.routers.klz-cables.service=klz-cables" + - "traefik.http.services.klz-cables.loadbalancer.server.port=80" + - "traefik.http.services.klz-cables.loadbalancer.server.scheme=http" + # Forwarded Headers (für Apps, die HTTPS erwarten) + - "traefik.http.middlewares.klz-forward.headers.customrequestheaders.X-Forwarded-Proto=https" + - "traefik.http.middlewares.klz-forward.headers.customrequestheaders.X-Forwarded-Ssl=on" + # Middlewares anhängen + - "traefik.http.routers.klz-cables.middlewares=klz-forward,compress" + app: image: registry.infra.mintel.me/mintel/klz-cables.com:latest restart: always @@ -20,26 +66,7 @@ services: # Redis (app-spezifischer DB-Index) - REDIS_URL=${REDIS_URL:-redis://redis:6379/2} - REDIS_KEY_PREFIX=${REDIS_KEY_PREFIX:-klz:} - labels: - - "traefik.enable=true" - # HTTP → HTTPS redirect (Challenge-Schutz für ALLE) - - "traefik.http.routers.klz-cables-web.rule=(Host(`klz-cables.com`) || Host(`www.klz-cables.com`) || Host(`staging.klz-cables.com`)) && !PathPrefix(`/.well-known/acme-challenge/`)" - - "traefik.http.routers.klz-cables-web.entrypoints=web" - - "traefik.http.routers.klz-cables-web.middlewares=redirect-https" - # HTTPS router (für ALLE drei Domains) - - "traefik.http.routers.klz-cables.rule=Host(`klz-cables.com`) || Host(`www.klz-cables.com`) || Host(`staging.klz-cables.com`)" - - "traefik.http.routers.klz-cables.entrypoints=websecure" - - "traefik.http.routers.klz-cables.tls.certresolver=le" - - "traefik.http.routers.klz-cables.tls=true" - - "traefik.http.routers.klz-cables.service=klz-cables" - - "traefik.http.services.klz-cables.loadbalancer.server.port=3000" - - "traefik.http.services.klz-cables.loadbalancer.server.scheme=http" - # Forwarded Headers (für Apps, die HTTPS erwarten) - - "traefik.http.middlewares.klz-forward.headers.customrequestheaders.X-Forwarded-Proto=https" - - "traefik.http.middlewares.klz-forward.headers.customrequestheaders.X-Forwarded-Ssl=on" - # Middlewares anhängen - - "traefik.http.routers.klz-cables.middlewares=klz-forward,compress" networks: infra: - external: true \ No newline at end of file + external: true diff --git a/varnish/default.vcl b/varnish/default.vcl new file mode 100644 index 00000000..c88eed17 --- /dev/null +++ b/varnish/default.vcl @@ -0,0 +1,90 @@ +vcl 4.1; + +# Minimal, safe Varnish config for a Next.js-style app. +# - Cache static assets aggressively +# - Avoid caching HTML/auth/api by default +# - Preserve websockets / upgrade + +backend default { + .host = "app"; + .port = "3000"; +} + +sub vcl_recv { + # Health endpoint should always work. + if (req.url == "/health") { + return (pass); + } + + # Websocket / Upgrade should not be cached. + if (req.http.Upgrade ~ "(?i)websocket") { + return (pipe); + } + + # Only cache GET/HEAD. + if (req.method != "GET" && req.method != "HEAD") { + return (pass); + } + + # If cookies are present, do not cache (safe default). + if (req.http.Cookie) { + return (pass); + } + + # Never cache Next.js data requests (often personalized) unless you explicitly want to. + if (req.url ~ "^/_next/data/") { + return (pass); + } + + # Cache immutable build assets. + if (req.url ~ "^/_next/static/") { + unset req.http.Cookie; + return (hash); + } + + # Cache common static files. + if (req.url ~ "\.(?:css|js|mjs|map|png|jpg|jpeg|gif|webp|svg|ico|woff2?|ttf|otf)$") { + unset req.http.Cookie; + return (hash); + } + + # Default: don't cache HTML. + return (pass); +} + +sub vcl_backend_response { + # Cache immutable Next build assets for a long time. + if (bereq.url ~ "^/_next/static/") { + set beresp.ttl = 365d; + set beresp.grace = 1h; + set beresp.http.Cache-Control = "public, max-age=31536000, immutable"; + unset beresp.http.Set-Cookie; + return (deliver); + } + + # Cache static files for 7 days (safe default). + if (bereq.url ~ "\.(?:css|js|mjs|map|png|jpg|jpeg|gif|webp|svg|ico|woff2?|ttf|otf)$") { + set beresp.ttl = 7d; + set beresp.grace = 1h; + if (!beresp.http.Cache-Control) { + set beresp.http.Cache-Control = "public, max-age=604800"; + } + unset beresp.http.Set-Cookie; + return (deliver); + } + + # Everything else: don't cache by default. + set beresp.ttl = 0s; + set beresp.uncacheable = true; + return (deliver); +} + +sub vcl_deliver { + # Helpful debug header; remove if you don't want this visible. + if (obj.hits > 0) { + set resp.http.X-Cache = "HIT"; + } else { + set resp.http.X-Cache = "MISS"; + } +} +