Skip to content

feat(docker): prekompresja gzip plików statycznych - #730

Merged
mpasternak merged 1 commit into
devfrom
feat/prekompresja-gzip-staticow
Aug 6, 2026
Merged

feat(docker): prekompresja gzip plików statycznych#730
mpasternak merged 1 commit into
devfrom
feat/prekompresja-gzip-staticow

Conversation

@mpasternak

Copy link
Copy Markdown
Member

Problem

Nginx w bpp-deploy ma gzip_static on włączone od dawna. Ta dyrektywa nie znaczy „kompresuj statyki" — znaczy „wyślij x.js.gz, jeśli istnieje".

W obrazie nie było ani jednego .gz (zmierzone na 202608.1399rc5: 3429 plików, 0 sztuk). Nginx cicho spadał na gzip on i kompresował plotly.min.js (4,4 MB) od nowa przy każdym cache-missie.

Objawu nie ma żadnego: strona działa, Content-Encoding jest, różni się tylko CPU brzegu i poziom kompresji (w locie 5, offline 9).

Rozwiązanie — dwa kroki, bo jeden nie wystarczał

1. R16 w docker/bpp_base/Dockerfilegzip -9 na staticroot.baked zaraz po collectstatic.
Zmierzone: 1813 plików, 2 s buildu, +12,5 MB do obrazu.

2. docker/appserver/entrypoint-appserver.sh — gzip na $STATIC_ROOT/CACHE po manage.py compress.

Drugi krok jest konieczny, bo bare.html pakuje bundle.js (786 KB) w {% compress js %} — przeglądarka pobiera CACHE/js/output.<hash>.js, plik, który powstaje dopiero w runtime i którego build-time nie widzi. Trzy największe assety są odwrotnie: plotly.min.js (4,4 MB), three-bundle.js (1,9 MB) i cytoscape-bundle.js (886 KB) są ładowane przez {% static %} poza blokami {% compress %}, czyli wprost z .baked.

Dlaczego build-time, a nie krok po stronie wdrożenia

Nginx serwując x.js.gz nie porównuje mtime z x.js. Nieaktualny .gz = wieczne serwowanie starego JS-a wszystkim przeglądarkom, bez śladu w logach; operator zgłasza to jako „zaktualizowałem i nic się nie zmieniło".

Generowanie .gz w tym samym kroku co plik źródłowy usuwa problem z definicji — nie ma okna, w którym mogłyby się rozjechać. W CACHE/ dodatkowo chroni content-hash w nazwie (zmiana treści = nowa nazwa, nie nadpisanie).

Zabezpieczenia

  • -size +1000c odpowiada gzip_min_length 1000 z konfiguracji nginksa.
  • Kompresujemy wyłącznie formaty tekstowewoff/png/webp są już skompresowane, .gz tylko zajmowałby miejsce.
  • Drugi find kasuje .gz, który wyszedł nie mniejszy od oryginału (nginx serwowałby większy plik klientom deklarującym gzip). Na obecnym drzewie takich przypadków jest 0 — to zabezpieczenie na przyszłe assety.
  • Błąd w entrypoincie nie eskaluje (skrypt leci z sh -e) — degraduje się miękko do gzipu w locie, ale loguje głośno, żeby regresja nie była niewidoczna.

Weryfikacja

Fragmenty wykonane na prawdziwym drzewie z opublikowanego obrazu, dokładnie w postaci, w jakiej wykona je Docker:

  • exit 0 pod set -e, 1813 plików .gz;
  • integralność: gzip -dc | cmp na losowej próbce — zgodne bajt w bajt;
  • guard rozmiaru sprawdzony podłożonym plikiem nieściśliwym (usunięty poprawnie, bundle.js.gz nietknięty) — żeby nie był martwym kodem;
  • entrypoint przetestowany w trzech ścieżkach: CACHE z plikami / brak CACHE / find z błędem — nigdzie nie wywraca startu.

Kompresja kluczowych assetów: plotly.min.js 4558732 → 1331125 B, three-bundle.js 1942818 → 424346 B, bundle.js 805055 → 226887 B.

Nie budowałem pełnego obrazu (kilkuminutowy build z yarn/uv) — pierwszy build w CI potwierdzi resztę.

Stage testserver (dev) celowo pominięty: gzip_static to funkcja nginksa, a dev serwuje statyki Django.

Powiązane

Sparowany z iplweb/bpp-deploy#31, który zapisuje dowody i werdykt w sprawie brotli/zstd (odrzucone — trzy blokady po stronie obrazu CRS). Tamten PR nie zmienia zachowania; ten włącza gzip_static realnie.

🤖 Generated with Claude Code

https://claude.ai/code/session_01J35fM3toviqNDsGxRg6P1c

Nginx w bpp-deploy ma `gzip_static on` wlaczone od dawna, ale ta dyrektywa
nie znaczy "kompresuj statyki" — znaczy "wyslij x.js.gz, JESLI istnieje".
W obrazie nie bylo ani jednego .gz (3429 plikow, 0 sztuk), wiec nginx cicho
spadal na `gzip on` i kompresowal plotly.min.js (4,4 MB) od nowa przy kazdym
cache-missie. Brak objawow: strona dziala, Content-Encoding jest, rozni sie
tylko CPU brzegu i poziom kompresji (w locie 5, offline 9).

Dwa kroki, bo jeden nie wystarczal:

1. R16 w bpp_base/Dockerfile — gzip -9 na staticroot.baked zaraz po
   collectstatic. Zmierzone: 1813 plikow, 2 s buildu, +12,5 MB do obrazu.
2. entrypoint-appserver.sh — gzip na $STATIC_ROOT/CACHE po `manage.py
   compress`. Ten krok jest konieczny, bo bare.html pakuje bundle.js (786 KB)
   w {% compress js %}, czyli przegladarka pobiera CACHE/js/output.<hash>.js
   — plik, ktory POWSTAJE DOPIERO W RUNTIME i ktorego build-time nie widzi.
   Trzy najwieksze assety (plotly 4,4 MB, three 1,9 MB, cytoscape 886 KB) sa
   odwrotnie: ladowane przez {% static %} POZA blokami compress, czyli wprost
   z .baked.

Dlaczego build-time, a nie krok runtime po stronie wdrozenia: nginx serwujac
x.js.gz NIE porownuje mtime z x.js. Nieaktualny .gz = wieczne serwowanie
starego JS-a wszystkim przegladarkom, bez sladu w logach. Generowanie .gz
w tym samym kroku co plik zrodlowy usuwa to z definicji; w CACHE/ dodatkowo
chroni content-hash w nazwie.

Zabezpieczenia: -size +1000c odpowiada gzip_min_length 1000 z konfiguracji
nginksa; kompresujemy wylacznie formaty tekstowe (woff/png/webp sa juz
skompresowane); drugi find kasuje .gz, ktory wyszedl nie mniejszy od
oryginalu. Blad w entrypoincie NIE eskaluje (skrypt leci z `sh -e`) —
degraduje sie miekko do gzip w locie, ale loguje glosno.

Zweryfikowane na prawdziwym drzewie z opublikowanego obrazu: exit 0 pod
set -e, 1813 plikow, integralnosc `gzip -dc | cmp` na probce OK, guard
rozmiaru sprawdzony podlozonym plikiem niescisliwym, entrypoint przetestowany
w trzech sciezkach (CACHE z plikami / brak CACHE / find z bledem).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J35fM3toviqNDsGxRg6P1c
@mpasternak
mpasternak merged commit 50feee2 into dev Aug 6, 2026
22 checks passed
@mpasternak
mpasternak deleted the feat/prekompresja-gzip-staticow branch August 6, 2026 12:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant