Automatyzacja wdrożeń nie musi być skomplikowana

Kiedy słyszymy o continuous deployment (CD), wyobraźnia podsuwa nam rozbudowane pipeline'y Jenkins'a, klastry Kubernetes i zespoły DevOps liczące więcej osób niż programistów. Taka wizja skutecznie odstrasza małe firmy od automatyzacji wdrożeń — a szkoda, bo podstawowy CD pipeline można postawić w kilka godzin i utrzymywać praktycznie bezkosztowo.

W naszej infrastrukturze obsługujemy kilka serwisów webowych na jednym serwerze VPS. Nie mamy dedykowanego zespołu DevOps ani budżetu na Kubernetes. Mimo to każda zmiana w kodzie trafia na produkcję w ciągu minut, bez ręcznego logowania się na serwer. Oto jak to robimy.

Czym właściwie jest continuous deployment?

Warto rozróżnić trzy pojęcia, które często są mylone:

  • Continuous Integration (CI) — automatyczne budowanie i testowanie kodu po każdym commit'cie
  • Continuous Delivery — kod jest zawsze gotowy do wdrożenia, ale wdrożenie wymaga ręcznego zatwierdzenia
  • Continuous Deployment — każda zmiana, która przejdzie testy, jest automatycznie wdrażana na produkcję

Dla małych firm z 1-3 programistami pełny continuous deployment bywa zbyt agresywny. Często lepszym wyborem jest continuous delivery z prostym mechanizmem ręcznego wyzwalania wdrożenia — na przykład tagiem Git lub kliknięciem przycisku.

Najprostszy pipeline: Git + SSH + Docker

Nasz minimalny pipeline CD składa się z trzech elementów:

  1. GitHub Actions (lub GitLab CI) — buduje obraz Docker po push'u do gałęzi main
  2. Docker Hub lub GitHub Container Registry — przechowuje zbudowane obrazy
  3. SSH — łączy się z serwerem produkcyjnym i pobiera nowy obraz

Przykładowy workflow dla GitHub Actions:

name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build and push Docker image
        run: |
          docker build -t ghcr.io/myorg/myapp:latest .
          echo "${{ secrets.GHCR_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
          docker push ghcr.io/myorg/myapp:latest

      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /home/www
            docker compose pull myapp
            docker compose up -d myapp
            docker compose restart nginx

To dosłownie cały pipeline. Mniej niż 30 linii konfiguracji, zero dodatkowych narzędzi, zero kosztów (GitHub Actions oferuje 2000 minut miesięcznie za darmo dla repozytoriów publicznych, a 500 minut dla prywatnych).

Alternatywa: deploy bez rejestru obrazów

Jeśli nie chcesz korzystać z rejestru Docker, możesz budować obraz bezpośrednio na serwerze:

ssh user@server "cd /home/www && git pull && docker compose up -d --build myapp && docker compose restart nginx"

Ta metoda jest prostsza, ale ma wadę — wymaga obecności kodu źródłowego i narzędzi budowania na serwerze produkcyjnym. Dla małych projektów to akceptowalne, ale z perspektywy bezpieczeństwa preferujemy podejście z rejestrem.

Strategia rollbacków

Każdy system CD musi mieć plan na wypadek wadliwego wdrożenia. Oto nasze podejście:

Tagowanie obrazów — oprócz tagu latest każdy obraz otrzymuje tag z hashem commit'u lub datą. Dzięki temu zawsze możemy wrócić do konkretnej wersji:

docker compose pull myapp
# Jeśli coś poszło nie tak:
docker compose down myapp
docker tag ghcr.io/myorg/myapp:previous ghcr.io/myorg/myapp:latest
docker compose up -d myapp

Health checks — w docker-compose.yml definiujemy health check dla każdego serwisu:

healthcheck:
  test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080/"]
  interval: 30s
  timeout: 5s
  retries: 3

Jeśli kontener nie przejdzie health check'a po restarcie, Docker oznaczy go jako unhealthy, co ułatwia monitorowanie.

Powiadomienia — po każdym wdrożeniu wysyłamy notyfikację (e-mail, Slack, webhook) z informacją o statusie. Jeśli wdrożenie się nie powiodło, dowiadujemy się o tym natychmiast.

Czego unikać

Na podstawie naszych doświadczeń i błędów, kilka ostrzeżeń:

  • Nie wdrażaj w piątek wieczorem — to może być żart, ale jest w nim dużo prawdy. Nawet z rollbackiem, debugowanie o północy w weekend to nie jest idealna sytuacja.
  • Nie pomijaj testów — nawet jeśli nie masz rozbudowanego zestawu testów jednostkowych, dodaj przynajmniej smoke test — jedno zapytanie HTTP sprawdzające, czy aplikacja odpowiada.
  • Nie ignoruj logów — po każdym wdrożeniu sprawdź logi przez kilka minut. Wiele problemów ujawnia się dopiero pod rzeczywistym ruchem.
  • Nie buduj na produkcji — kompilacja na serwerze produkcyjnym zużywa zasoby, które powinny służyć użytkownikom. Buduj obrazy w CI i przesyłaj gotowe artefakty.

Koszty i narzędzia

Pełny pipeline CD dla małej firmy może kosztować dokładnie 0 zł:

| Narzędzie | Koszt | Uwagi | |---|---|---| | GitHub Actions | 0 zł | 500+ minut/miesiąc | | GitHub Container Registry | 0 zł | 500 MB storage | | SSH | 0 zł | Wbudowane w serwer | | Docker Compose | 0 zł | Open source |

Jedyny realny koszt to czas konfiguracji — kilka godzin na początku, a potem sporadyczne poprawki. W zamian zyskujesz wdrożenia trwające minuty zamiast godzin, powtarzalność procesu i spokój ducha.

Kiedy warto rozważyć coś więcej

Prosty pipeline Git + Docker + SSH przestaje wystarczać, gdy:

  • Masz więcej niż 3-4 serwisy z zależnościami między sobą
  • Potrzebujesz wdrożeń blue-green lub canary
  • Twoja aplikacja wymaga migracji bazy danych przy każdym wdrożeniu
  • Masz więcej niż jeden serwer produkcyjny

W takich przypadkach warto rozważyć narzędzia takie jak Coolify (self-hosted PaaS), Kamal (od twórców Ruby on Rails) lub — jeśli skala to uzasadnia — Kubernetes.

Ale dla 90% małych firm i startupów prosty pipeline opisany w tym artykule to wszystko, czego potrzeba. Napisz do nas, jeśli chcesz pomocy w skonfigurowaniu automatycznych wdrożeń dla Twojego projektu.