Wdrożenie aplikacji webowych w Dockerze i CI/CD

Kacper Sieradziński
Kacper Sieradziński
10 maja 2025Edukacja5 min czytania

Wdrażanie aplikacji webowych w Pythonie (Django, Flask, FastAPI) to proces, który wymaga konsekwencji, standaryzacji i automatyzacji. Docker pozwala „spakować" Twoją aplikację w powtarzalny kontener, a CI/CD automatyzuje całą ścieżkę od testów po publikację. Efekt? Mniej błędów, szybsze wdrożenia, większa stabilność. W tym przewodniku poznasz, jak krok po kroku skonfigurować Docker i CI/CD dla aplikacji Python, od podstawowej konfiguracji kontenera po zaawansowane strategie deploymentu w chmurze.

Obraz główny Wdrożenie aplikacji webowych w Dockerze i CI/CD

Dlaczego Docker?

Docker to technologia, która pozwala uruchamiać aplikacje w kontenerach – izolowanych środowiskach działających identycznie na każdym serwerze. Zamiast konfigurować zależności ręcznie na każdym serwerze, opisujesz środowisko w pliku Dockerfile, a Docker zajmuje się resztą.

Główne zalety Docker

  • Spójność środowisk – koniec z „u mnie działa". Kontener działa identycznie na maszynie deweloperskiej, serwerze testowym i produkcyjnym.

  • Łatwe wdrażanie – cały stack startuje jednym poleceniem. Nie musisz ręcznie konfigurować Pythona, bibliotek czy bazy danych.

  • Szybsze testy i CI/CD – pipeline działa na identycznym obrazie, co produkcja. Masz pewność, że testy przechodzą w rzeczywistym środowisku.

  • Skalowalność – gotowe pod Kubernetes lub chmurę. Możesz łatwo uruchomić wiele instancji aplikacji na różnych serwerach.

Docker rozwiązuje problem różnic między środowiskami poprzez enkapsulację całego środowiska aplikacji w kontenerze, który działa identycznie na każdym komputerze z zainstalowanym Dockerem.

Tworzenie obrazu aplikacji Python

Zacznij od pliku Dockerfile w katalogu głównym projektu. Dockerfile to przepis na obraz kontenera – opisuje, jak zbudować środowisko dla Twojej aplikacji.

Podstawowy Dockerfile

Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 # Dockerfile FROM python:3.12-slim ENV PYTHONUNBUFFERED=1 WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["gunicorn", "app.wsgi:application", "--bind", "0.0.0.0:8000"]

Ten plik:

  • bazuje na oficjalnym obrazie Pythona (3.12-slim jest lekki i bezpieczny),
  • instaluje zależności z requirements.txt,
  • kopiuje kod źródłowy aplikacji,
  • uruchamia aplikację przez Gunicorn, czyli produkcyjny serwer WSGI (zamiast wbudowanego serwera deweloperskiego Django/Flask).

PYTHONUNBUFFERED=1 zapewnia, że logi są wyświetlane w czasie rzeczywistym, a --no-cache-dir zmniejsza rozmiar obrazu.

Budowanie obrazu

Bash
1 docker build -t myapp .

Flaga -t nadaje obrazowi nazwę. Kropka na końcu oznacza, że Docker szuka Dockerfile w bieżącym katalogu.

Uruchamianie kontenera

Bash
1 docker run -p 8000:8000 myapp

Flaga -p 8000:8000 mapuje port 8000 z kontenera na port 8000 hosta, więc aplikacja będzie dostępna pod adresem http://localhost:8000.

Docker Compose – pełna infrastruktura

Większość aplikacji wymaga kilku usług: bazy danych, cache, reverse proxy. Zamiast uruchamiać je ręcznie lub pisać skrypty, zdefiniuj wszystko w jednym pliku docker-compose.yml.

Przykładowa konfiguracja docker-compose.yml

YAML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 version: '3.9' services: web: build: . ports: - "8000:8000" env_file: - .env depends_on: - db - redis command: gunicorn app.wsgi:application --bind 0.0.0.0:8000 volumes: - .:/app - static_volume:/app/staticfiles db: image: postgres:15 environment: POSTGRES_USER: django POSTGRES_PASSWORD: secret POSTGRES_DB: django_db volumes: - postgres_data:/var/lib/postgresql/data redis: image: redis:7 volumes: - redis_data:/data nginx: image: nginx:latest ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf - static_volume:/app/staticfiles - ./media:/app/media depends_on: - web volumes: postgres_data: redis_data: static_volume:

Uruchomienie środowiska

Bash
1 docker-compose up --build

Masz kompletne środowisko: Django/Flask/FastAPI + PostgreSQL + Redis + Nginx — wszystko w jednym poleceniu. Flaga --build wymusza przebudowę obrazów, co jest przydatne po zmianach w Dockerfile.

Docker Compose automatycznie:

  • tworzy sieć dla komunikacji między kontenerami,
  • uruchamia serwisy w odpowiedniej kolejności (według depends_on),
  • zarządza wolumenami dla trwałego przechowywania danych.

Zmienna konfiguracja i tajne dane

Hasła, klucze API czy dane dostępowe nie mogą trafiać do repozytorium. Trzymaj je w pliku .env, który jest ignorowany przez Git (dodaj .env do .gitignore).

Plik .env

INI
1 2 3 4 5 DEBUG=False SECRET_KEY=super_secret_key_here DATABASE_URL=postgres://django:secret@db:5432/django_db REDIS_URL=redis://redis:6379/0 ALLOWED_HOSTS=example.com,www.example.com

Wczytywanie zmiennych w Pythonie

A następnie wczytaj je w Pythonie (np. przez python-decouple):

Bash
1 pip install python-decouple

W settings.py (Django) lub konfiguracji aplikacji:

Python
1 2 3 4 5 6 7 8 9 10 11 from decouple import config SECRET_KEY = config('SECRET_KEY') DEBUG = config('DEBUG', cast=bool, default=False) DATABASE_URL = config('DATABASE_URL') # Parsowanie DATABASE_URL dla Django import dj_database_url DATABASES = { 'default': dj_database_url.config(default=DATABASE_URL) }

W środowisku produkcyjnym używaj zmiennych środowiskowych serwera lub zarządzanych sekretów (np. AWS Secrets Manager, HashiCorp Vault, GitHub Secrets dla CI/CD).

CI/CD – automatyzacja wdrażania

CI/CD (Continuous Integration / Continuous Deployment) to proces, który testuje Twój kod przy każdym commicie, buduje obrazy Dockera i automatycznie wdraża aplikację po przejściu testów. To eliminuje ręczne kroki i zmniejsza ryzyko błędów.

Przykład GitHub Actions workflow

Stwórz plik .github/workflows/deploy.yml:

YAML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 name: CI/CD Pipeline on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build-test-deploy: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: 3.12 - name: Install dependencies run: | pip install -r requirements.txt pip install pytest - name: Run tests run: | pytest - name: Run linting run: | pip install flake8 black flake8 . black --check . - name: Build Docker image run: | docker build -t myapp:latest . - name: Deploy container if: github.ref == 'refs/heads/main' && github.event_name == 'push' run: | docker-compose down docker-compose up -d --build

Ten pipeline:

  • sprawdza kod z repozytorium,
  • uruchamia testy jednostkowe (pytest),
  • sprawdza jakość kodu (linting),
  • buduje obraz Dockera,
  • wdraża go automatycznie na serwer (tylko dla brancha main i pushów, nie dla pull requestów).

W prawdziwym projekcie dodaj też:

  • testy integracyjne,
  • etapy dla środowiska staging,
  • rollback w przypadku błędów,
  • powiadomienia o statusie wdrożenia (Slack, e-mail),
  • deployment do chmury (AWS, GCP, Azure).

Dobre praktyki DevOps dla Pythona

Przy projektowaniu systemu wdrożeniowego warto pamiętać o następujących zasadach:

  1. Oddziel środowiska – osobne konfiguracje dla developmentu, stagingu i produkcji. Używaj osobnych plików .envdocker-compose dla każdego środowiska (np. docker-compose.dev.yml, docker-compose.prod.yml).

  2. Używaj .env i secrets – nigdy nie commituj haseł, kluczy API i tokenów. Wszystkie wrażliwe dane powinny być w zmiennych środowiskowych lub zarządzanych sekretach.

  3. Buduj lekkie obrazy – używaj python:slim lub python:alpine zamiast pełnego obrazu Pythona. To zmniejsza rozmiar obrazu i przyspiesza deployment.

  4. Cache'uj zależności – optymalizuj Dockerfile, aby warstwy z zależnościami były cache'owane:

Dockerfile
1 2 3 4 5 6 # Kopiuj requirements.txt najpierw (częściej zmienia się kod niż zależności) COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Dopiero potem kopiuj kod COPY . .
  1. Uruchamiaj testy automatycznie przed deployem – blokuj deployment, jeśli testy nie przechodzą lub kod nie spełnia standardów jakości.

  2. Monitoruj aplikację – używaj narzędzi takich jak Prometheus + Grafana do monitorowania metryk, lub Sentry do śledzenia błędów. To pozwala szybko reagować na problemy w produkcji.

  3. Loguj wszystko – dostęp, błędy, metryki, deploye. Centralizowane logowanie (np. ELK Stack, Loki) ułatwia debugowanie i analizę.

  4. Używaj multi-stage build – dla bardziej zaawansowanych zastosowań użyj multi-stage builds, aby zmniejszyć rozmiar finalnego obrazu:

Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 # Stage 1: Build FROM python:3.12 as builder WORKDIR /app COPY requirements.txt . RUN pip install --user -r requirements.txt # Stage 2: Runtime FROM python:3.12-slim WORKDIR /app COPY --from=builder /root/.local /root/.local COPY . . ENV PATH=/root/.local/bin:$PATH CMD ["gunicorn", "app.wsgi:application", "--bind", "0.0.0.0:8000"]

Skalowanie i integracja z chmurą

Docker to świetny wstęp do większej automatyzacji. Kiedy aplikacja rośnie, możesz wykorzystać bardziej zaawansowane narzędzia:

Kubernetes (K8s)

Zarządzanie wieloma kontenerami w klastrze. Kubernetes pozwala na automatyczne skalowanie, load balancing i wysoką dostępność. To następny krok po Docker Compose dla większych aplikacji.

AWS ECS / Google Cloud Run

Serverless uruchamianie kontenerów w chmurze. Cloud Run automatycznie skaluje kontenery w zależności od obciążenia, a płacisz tylko za rzeczywiste użycie zasobów.

GitOps (ArgoCD)

Deklaratywne zarządzanie wdrożeniami. Zmiany w repozytorium automatycznie synchronizują się z środowiskiem produkcyjnym. Git staje się źródłem prawdy dla infrastruktury.

CI/CD z rollbackiem

Wdrożenie z automatycznym cofaniem zmian po błędzie. Zaawansowane pipeline'y mogą automatycznie wykrywać problemy (np. wzrost błędów, spowolnienie) i cofać zmiany bez interwencji człowieka.

Dzięki tym narzędziom aplikacja działa stabilnie niezależnie od ilości użytkowników, a infrastruktura jest zarządzana automatycznie i deklaratywnie.

Podsumowanie

Docker i CI/CD to dziś standard w profesjonalnym wdrażaniu aplikacji Python. Dają powtarzalność, bezpieczeństwo i pełną automatyzację. W połączeniu z testami jednostkowymi i dobrym monitoringiem pozwalają na pewne, szybkie i skalowalne wdrożenia.

Nie ma znaczenia, czy tworzysz mikroserwis w FastAPI, bloga w Django czy API w Flasku — jeśli chcesz wdrażać jak zawodowiec, Docker i CI/CD to obowiązkowy zestaw. Zacznij od prostego Dockerfile i podstawowego pipeline'u CI, a następnie rozbuduj system o zaawansowane funkcje w miarę potrzeb projektu. Pamiętaj, że wdrożenie to proces iteracyjny — zaczynaj od prostych rozwiązań i ulepszaj je systematycznie.