Artykuł Technologie

Docker Compose - podstawy dla self-hosted AI

Docker Compose to narzędzie do uruchamiania aplikacji złożonych z wielu kontenerów na jednej maszynie za pomocą jednego pliku YAML (docker-compose.yml). Zamiast wielu poleceń docker run opisujesz cały stack deklaratywnie i startujesz komendą docker compose up -d - przy self-hostingu AI składasz tak Ollama i interfejs w jeden działający zestaw.

W skrócie: Compose działa świetnie lokalnie na laptopie, ale produkcyjny stack AI musi chodzić 24/7 - wtedy potrzebujesz VPS pod n8n i lokalne LLM

W skrócie: Docker to silnik, który uruchamia pojedynczy kontener, a Docker Compose to nakładka, która zarządza wieloma kontenerami naraz na jednej maszynie. Compose nie zastępuje Dockera - opakowuje go. Zamiast ręcznie odpalać osobno kontener z bazą, osobno z aplikacją i osobno z interfejsem, opisujesz je wszystkie w jednym pliku i uruchamiasz razem.

Konkret: Compose to nie Kubernetes. Kubernetes orkiestruje kontenery na klastrze wielu maszyn (węzłów), a Compose pracuje na jednej maszynie. Dla domowego serwera, homelaba czy pojedynczego VPS to dokładnie to, czego potrzebujesz - bez narzutu i bez nadmiarowej złożoności. Sam Docker Compose to narzędzie stworzone przez Docker, Inc., a do definiowania środowiska używa formatu YAML.

Dla porządku: Compose nie ma też nic wspólnego z Docker Swarm, czyli starszą warstwą do orkiestracji w klastrze, która korzystała z dawnego formatu pliku Compose. Po przejęciu Swarma przez Mirantis nie jest on już rozwijany przez Docker, Inc. - dla pojedynczej maszyny i tak interesuje Cię Compose, a nie Swarm.

W klasycznych poradnikach Compose najczęściej widać usługi typu redis, nginx czy postgres - to typowy zestaw aplikacji webowej (cache, serwer WWW, baza danych). My zbudujemy zamiast tego stack AI, ale mechanika pliku jest dokładnie taka sama: jedna usługa to jeden kontener, niezależnie od tego, czy w środku siedzi baza, serwer WWW, czy silnik LLM.

Compose V2 (docker compose) vs V1 (docker-compose)

To częste źródło zamieszania, bo w sieci krążą obie formy zapisu. Różnica jest konkretna:

  • docker compose (ze spacją) to wersja V2 - wtyczka zintegrowana z Docker CLI. To wersja aktualna i tej powinieneś używać.
  • docker-compose (z myślnikiem) to stara wersja V1, napisana w Pythonie i dostępna w gałęzi v1. Jest uznana za przestarzałą.

Pro tip admina: składnia samego pliku docker-compose.yml jest w obu wariantach praktycznie taka sama, więc starsze poradniki z komendą docker-compose up nadal działają koncepcyjnie - po prostu zamień myślnik na spację. Obie formy krążą w starszych tutorialach i wpisach, ale realnie pracujesz dziś na V2.

Kiedy Compose, a kiedy docker run

Trade-off jest prosty:

  • Jeden kontener bez zależności (np. szybki test obrazu) - docker run wystarczy.
  • Stack z dwóch lub więcej kontenerów, które muszą się widzieć (np. Ollama plus interfejs) - Compose. Masz wtedy całą konfigurację w pliku pod kontrolą wersji, zamiast w historii terminala.

Instalacja Docker Compose

Krok po kroku (Linux, np. Ubuntu albo Debian na VPS): w wersji V2 Compose instaluje się jako wtyczka razem z silnikiem Dockera. W oficjalnym repozytorium Dockera odpowiada za to pakiet docker-compose-plugin, który zwykle dociągasz przy instalacji Docker Engine.

Sprawdź instalację:

docker compose version

Jeśli polecenie zwraca numer wersji, masz V2 gotowe do pracy. Na Windowsie i macOS nie musisz instalować nic osobno - Docker Compose jest dołączony do pakietu Docker Desktop.

Dokładne polecenia instalacji bywają aktualizowane wraz z wersjami systemu i repozytoriów - aktualną procedurę dla swojej dystrybucji sprawdź w aktualnej dokumentacji Dockera (docs.docker.com).

Błąd "docker: 'compose' is not a docker command"

Objaw: po wpisaniu docker compose version terminal zwraca komunikat, że compose nie jest poleceniem Dockera. Przyczyna: masz silnik Dockera, ale brakuje wtyczki Compose V2. Rozwiązanie: doinstaluj pakiet docker-compose-plugin z repozytorium Dockera i ponów weryfikację. Jeśli dopiero stawiasz Ollama w kontenerze, cały proces od zera opisuję w osobnym wpisie - jak postawić Ollama w kontenerze to jeden kontener. Podajesz obraz (image), nazwę kontenera (container_name) i politykę restartu (restart).

services:
  ollama:
    image: ollama/ollama
    container_name: ollama
    restart: unless-stopped

Konkret: restart: unless-stopped sprawia, że kontener wstaje sam po restarcie serwera - kluczowe dla stacku, który ma działać 24/7.

ports - mapowanie portów

Port to brama do kontenera z zewnątrz. Składnia to host:kontener.

    ports:
      - "11434:11434"

Lewa liczba to port na maszynie-hoście, prawa to port wewnątrz kontenera. Trade-off: ports wystawia usługę na zewnątrz, a expose udostępnia port tylko innym kontenerom w tej samej sieci, bez otwierania go na świat.

volumes - trwałość danych

Pułapka, na którą nabiera się każdy początkujący: bez wolumenu dane z kontenera znikają po jego usunięciu. Wolumen to trwały magazyn poza cyklem życia kontenera.

    volumes:
      - ollama_data:/root/.ollama

volumes:
  ollama_data:

Dwa typy: named volume (zarządzany przez Dockera, jak wyżej) oraz bind mount (mapowanie konkretnej ścieżki z hosta, np. ./dane:/root/.ollama). Konkret: pobrane modele Ollama trzymaj na named volume, żeby kilkugigabajtowe pliki nie znikały po każdym docker compose down.

environment - zmienne środowiskowe

Konfigurację kontenera podajesz przez zmienne środowiskowe - bezpośrednio albo z pliku .env.

    environment:
      - OLLAMA_HOST=0.0.0.0

Pro tip admina: hasła i klucze trzymaj w pliku .env (dodanym do .gitignore), a nie na sztywno w docker-compose.yml. Dzięki temu nie wrzucisz przypadkiem hasła do repozytorium.

networks - komunikacja między kontenerami

To zaskakuje wielu początkujących: Compose automatycznie tworzy wspólną sieć dla wszystkich usług z jednego pliku. Kontenery widzą się po nazwie usługi - nie po adresie IP.

Konkret: interfejs Open WebUI łączy się z Ollama nie przez localhost, tylko przez nazwę usługi - http://ollama:11434. Wewnątrz sieci Compose ollama to poprawna nazwa hosta wskazująca na węzeł z modelem.

depends_on i healthcheck - kolejność startu

depends_on ustala kolejność uruchamiania - np. interfejs ma wstać po silniku.

    depends_on:
      - ollama

Trade-off: depends_on czeka, aż kontener wystartuje, ale niekoniecznie aż usługa w środku będzie gotowa przyjmować połączenia. Do tego dochodzi healthcheck, który sprawdza realną gotowość usługi, zanim ruszą zależne kontenery.

Kompletny plik Ollama + Open WebUI

Złóżmy to w całość. Poniższy docker-compose.yml stawia lokalny stack AI: silnik Ollama plus webowy interfejs Open WebUI, połączone wspólną siecią Compose.

services:
  ollama:
    image: ollama/ollama
    container_name: ollama
    restart: unless-stopped
    ports:
      - "11434:11434"
    volumes:
      - ollama_data:/root/.ollama
    # silnik LLM - tu pobierają się i działają modele

  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: open-webui
    restart: unless-stopped
    ports:
      - "3000:8080"
    volumes:
      - open_webui_data:/app/backend/data
    environment:
      # interfejs łączy się z silnikiem po nazwie usługi, nie po localhost
      - OLLAMA_BASE_URL=http://ollama:11434
    depends_on:
      - ollama

volumes:
  ollama_data:
  open_webui_data:

Krok po kroku: zapisz plik jako docker-compose.yml, w tym samym katalogu uruchom docker compose up -d, a po chwili wejdź w przeglądarce na http://localhost:3000. Interfejs Open WebUI sam połączy się z Ollama po wspólnej sieci. Więcej o samym interfejsie znajdziesz we wpisie Open WebUI - interfejs do Ollama.

Podstawowe komendy Compose

Wszystkie polecenia uruchamiasz w katalogu z plikiem docker-compose.yml. Oto zestaw, który pokrywa codzienną pracę:

Polecenie Co robi
docker compose up -d Tworzy i uruchamia cały stack w tle (detached)
docker compose down Zatrzymuje i usuwa kontenery (dane na named volume zostają)
docker compose ps Pokazuje status uruchomionych usług
docker compose logs -f Wyświetla logi na żywo (-f = follow)
docker compose restart Restartuje usługi bez przebudowy obrazów
docker compose pull Pobiera nowsze wersje obrazów
docker compose build Buduje obrazy z lokalnego Dockerfile

Konkret: różnica między up a start. up tworzy kontenery od zera i je startuje, a start jedynie uruchamia kontenery, które już istnieją, ale zostały zatrzymane. Na co dzień używasz up -d.

Pułapka: docker compose down -v. Flaga -v kasuje także named volumes, czyli Twoje dane, łącznie z pobranymi modelami. Używaj down bez -v, chyba że naprawdę chcesz wyczyścić wszystko do zera.

Częste błędy i pułapki

Większość problemów z Compose to nie awarie, tylko powtarzalne potknięcia. Oto te, które łapią najczęściej.

  • port is already allocated - port na hoście jest już zajęty przez inny proces (np. 11434 trzyma lokalnie zainstalowana Ollama). Rozwiązanie: zmień port po lewej stronie mapowania, np. "11435:11434", albo zatrzymaj proces zajmujący port.
  • Dane znikają po down - prawie zawsze oznacza brak named volume albo użycie down -v. Sprawdź, czy usługa ma podpięty wolumen i czy nie kasujesz go flagą.
  • Kontenery się nie widzą - zwykle literówka w nazwie usługi albo odwołanie do localhost zamiast nazwy usługi. Pamiętaj: wewnątrz sieci Compose łączysz się po nazwie (ollama), nie po localhost.
  • Ostrzeżenie o version - starsze pliki zaczynają się od pola version: "3". W Compose V2 to pole jest już zbędne i bywa zgłaszane jako przestarzałe - możesz je po prostu usunąć.
  • permission denied przy bind mount - kontener nie ma prawa zapisu do zamapowanej ścieżki hosta. Przyczyna to najczęściej niezgodność UID albo GID użytkownika w kontenerze i na hoście.

Sprawdź: jeśli błąd dotyczy konkretnie Ollama (modele się nie pobierają, GPU niewidoczne), więcej rozwiązań zebrałem w osobnym wpisie - jak postawić Ollama w kontenerze to wersja V2 - wtyczka zintegrowana z Docker CLI, której powinieneś używać dzisiaj. docker-compose (z myślnikiem) to stara wersja V1 napisana w Pythonie, uznana za przestarzałą. Składnia pliku jest praktycznie identyczna, więc starsze poradniki nadal działają - zamień tylko myślnik na spację w poleceniu.

Jak zainstalować Docker Compose na Ubuntu lub VPS?

W wersji V2 Compose instaluje się jako wtyczka razem z silnikiem Dockera - w repozytorium Dockera odpowiada za to pakiet docker-compose-plugin. Po instalacji zweryfikuj wszystko poleceniem docker compose version. Jeśli zwróci numer wersji, masz gotowe środowisko. Dokładną procedurę dla swojej dystrybucji sprawdź w aktualnej dokumentacji Dockera.

Gdzie Docker Compose przechowuje dane?

W wolumenach. Named volume jest zarządzany przez Dockera i przeżywa usunięcie kontenera, a bind mount mapuje konkretną ścieżkę z hosta do kontenera. Dla stacku AI trzymaj pobrane modele na named volume - dzięki temu kilkugigabajtowe pliki nie znikają po docker compose down i nie pobierasz ich od nowa.

Jak zatrzymać stack bez utraty danych?

Użyj docker compose down, bez flagi -v. Polecenie zatrzymuje i usuwa kontenery, ale named volumes z danymi pozostają nietknięte. Pułapka: docker compose down -v kasuje także wolumeny, czyli Twoje dane i pobrane modele. Flagi -v używaj wyłącznie, gdy świadomie chcesz wyczyścić stack do zera.

Jak kontenery komunikują się między sobą?

Compose automatycznie tworzy wspólną sieć dla wszystkich usług z jednego pliku. Kontenery łączą się po nazwie usługi, nie po adresie IP ani localhost. Przykład: interfejs łączy się z silnikiem przez http://ollama:11434, gdzie ollama to nazwa usługi. To wbudowane DNS Compose wskazuje na właściwy węzeł stacku.

Czy Docker Compose nadaje się na produkcję?

Tak, Compose działa produkcyjnie na pojedynczej maszynie i świetnie sprawdza się dla jednego serwera czy homelaba. Ograniczenie: to nie orkiestrator klastra jak Kubernetes. Stack AI działający 24/7 potrzebuje serwera z zapasem RAM, a często też GPU - lokalny laptop nie wystarczy do stałej pracy pod obciążeniem.

Co dalej

Teraz rozumiesz, z czego składa się docker-compose.yml i potrafisz jednym poleceniem postawić stack Ollama plus Open WebUI. Konkret: Compose świetnie sprawdza się lokalnie, ale produkcyjny zestaw AI musi chodzić bez przerwy, a do tego laptop nie wystarczy.

Trade-off: stały stack AI (silnik LLM, interfejs i automatyzacja w stylu n8n) potrzebuje serwera z zapasem RAM, a często też GPU dla sensownej prędkości generowania. Jeśli planujesz uruchomić go na stałe, sprawdź VPS pod n8n i lokalne LLM.