Wprowadzenie
Docker stał się standardem w nowoczesnym web development. Obietnica „działa u mnie” znika, a zamienia się w „działa wszędzie”. Brzmi pięknie, prawda? Problem w tym, że w małych firmach – bez dedykowanego DevOpsa – Docker często staje się źródłem bólu, a nie oszczędności. Widziałem to wiele razy: zespół zaczyna z entuzjazmem, a kończy na debugowaniu tajemniczych błędów sieciowych, niepotrzebnie rozdętych obrazach i kosztach chmury, które rosną w tempie wykładniczym. W tym artykule pokażę trzy konkretne pułapki, które sam spotkałem w projektach, oraz jak ich uniknąć.
1. Grube obrazy, które tyją każdego dnia
Problem
Większość początkujących użytkowników Dockera tworzy obrazy bazujące na pełnej dystrybucji Linuksa, np. ubuntu:latest. Do tego doinstalowują wszystkie możliwe narzędzia „na zapas”. Efekt? Obraz waży 800 MB, a aplikacja to proste API w Node.js. W projekcie, który audytowałem, obraz zajmował 1,2 GB – przez zbędne zależności, wieloetapowy build bez czyszczenia cache’u, a także pozostawione klucze SSH.
Dlaczego to boli?
- Wolne deploye: każda zmiana ciągnie za sobą pobranie 1 GB danych. CI/CD zwalnia, a developerzy czekają.
- Koszty przechowywania: w rejestrze obrazów (np. Docker Hub, AWS ECR) płacisz za miejsce. Grube obrazy = wyższe rachunki.
- Bezpieczeństwo: więcej warstw to większa powierzchnia ataku. Każda dodatkowa biblioteka to potencjalna podatność.
Rozwiązanie
- Używaj wieloetapowych buildów (multi-stage). Każdy etap może używać innej bazy, a finalny obraz zawiera tylko to, co niezbędne do uruchomienia aplikacji.
- Wybieraj lekkie bazy, np.
alpine,slimwarianty oficjalnych obrazów (np.node:18-alpine). - Usuwaj zbędne pliki tymczasowe w tej samej warstwie, w której je tworzysz (np.
apt-get clean && rm -rf /var/lib/apt/lists/*).
# Przykład poprawnego Dockerfile dla Node.js
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Dzięki temu obraz schudł z 1,2 GB do 180 MB. Wdech, wydech.
2. Zaniedbane sieci i komunikacja między kontenerami
Problem
W teorii Docker Compose łączy serwisy automatycznie. W praktyce widziałem projekty, gdzie każdy kontener komunikował się przez localhost lub 127.0.0.1. Albo gorzej – używano network_mode: host, co zabija izolację. Zazwyczaj wynika to z lenistwa lub niewiedzy, ale konsekwencje są realne.
Dlaczego to boli?
- Błędy w produkcji: aplikacja działa na lokalnym, ale na serwerze nagle nie może połączyć się z bazą danych. Dlaczego? Bo w kodzie zakodowano
localhost, a kontenery są w różnych sieciach. - Brak skalowalności: z
network_mode: hostnie uruchomisz dwóch instancji tego samego serwisu na różnych portach – to blokada dla replikacji. - Problemy z DNS: Docker udostępnia wewnętrzny DNS, ale jeśli go nie używasz, tracisz elastyczność.
Rozwiązanie
- Zawsze używaj nazw serwisów zdefiniowanych w docker-compose.yml jako hostname. Zamiast
localhost:27017piszmongodb:27017. - Unikaj
network_mode: host, chyba że masz konkretną potrzebę (np. wydajność sieci w specyficznych aplikacjach). - Twórz własne sieci, aby kontrolować izolację: np.
backendifrontend.
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
depends_on:
- db
networks:
- backend
db:
image: mongo:6
volumes:
- mongo-data:/data/db
networks:
- backend
networks:
backend:
volumes:
mongo-data:
Po tym zabiegu połączenia między serwisami stają się przewidywalne i łatwe do debugowania.
3. Brak strategii dla wolumenów i danych trwałych
Problem
Kontenery są efemeryczne – ich zamknięcie = utrata danych. Małe firmy często ignorują ten fakt i trzymają dane w kontenerze, licząc na cud. Przykład: uruchamiasz MySQL w kontenerze bez wolumenu, restartujesz go i… witaj, pustej bazie. Albo gorzej: używasz bind mount, ale przez nieodpowiednie uprawnienia kontener nie może zapisywać plików.
Dlaczego to boli?
- Utrata danych: najgorsza opcja – backupów nie ma, bo „przecież to tylko testy”.
- Problemy z uprawnieniami: przy bind mount, UID użytkownika w kontenerze nie zgadza się z UID na hoście, co powoduje błędy dostępu.
- Opóźnienia w rozwoju: developerzy tracą czas na odtwarzanie danych testowych.
Rozwiązanie
- Zawsze definiuj named volumes w docker-compose dla danych, które mają przetrwać restart kontenera.
- Unikaj bind mount dla kodu źródłowego w produkcji (choć w dev to wygodne). Zamiast tego używaj wolumenów do dzielenia się danymi.
- Ustal reguły backupu: np. rsync z wolumenu do zewnętrznego storage’u, albo regularne dumpy bazy.
services:
db:
image: postgres:15
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Dzięki temu dane są bezpieczne, a Ty śpisz spokojnie.
Podsumowanie
Docker to potężne narzędzie, ale w małych firmach łatwo o popełnienie kosztownych błędów. Grube obrazy, złe zarządzanie sieciami i ignorowanie trwałości danych to trzy obszary, które przysparzają najwięcej problemów. Upraszczając: pamiętaj o multi-stage buildach, używaj nazw serwisów zamiast localhosta i zawsze deklaruj wolumeny dla danych. Wprowadzenie tych dobrych praktyk zajmie Ci godzinę, a może uratować tygodnie frustracji.
Potrzebujesz wsparcia przy optymalizacji infrastruktury kontenerowej? JurskiTech pomaga małym firmom wdrażać DevOps z głową – bez przesadnego skomplikowania i z realnym przełożeniem na biznes.


