Blog

🚀 Produkcyjne CI/CD z Dockerem na GitLabie – Praktyczny Przewodnik

Ten przewodnik pokazuje, jak zbudować profesjonalny pipeline CI/CD na GitLabie dla aplikacji Laravel (lub innej), wykorzystując Dockera, buildx, testy, automatyczne budowanie obrazów i bezpieczne wdrożenie na serwer produkcyjny.

📋 Spis treści


1. Wstęp

GitLab CI/CD pozwala na pełną automatyzację procesu testowania, budowania i wdrażania aplikacji. W połączeniu z Dockerem możesz zapewnić powtarzalność buildów, szybkie testy i bezpieczne wdrożenia. Poniżej znajdziesz praktyczny opis pipeline’u, który działa w produkcji.


2. Struktura pipeline’u

Pełny przykład: .gitlab-ci.yml

Poniżej znajdziesz kompletny, gotowy do skopiowania przykład produkcyjnego pliku .gitlab-ci.yml dla projektu Laravel z Dockerem:

image: docker:latest

stages:
  - test
  - build
  - deploy
    
variables:
  DOCKER_BUILDKIT: 1
  DOCKER_HOST: tcp://docker:2375/
  DOCKER_TLS_CERTDIR: ""
  IMAGE_NAMESPACE: dommmin/laravel-production
  REGISTRY: registry.gitlab.com

services:
  - docker:dind

before_script:
  - docker info
  - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"

test_php:
  stage: test
  image: dommin/php-8.4-fpm:latest
  needs: [test_node]
  before_script:
    - git config --global --add safe.directory $(pwd)
    - composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader
  script:
    - cp .env.testing .env
    - php artisan key:generate
    - php artisan ziggy:generate
    - composer larastan
    - composer pint
    - php artisan test --env=testing
  variables:
    DB_CONNECTION: sqlite
    DB_DATABASE: ":memory:"
    SESSION_DRIVER: array
  artifacts:
    paths:
      - storage/logs/
    expire_in: 1 week
  only:
    - main
    - merge_requests

test_node:
  stage: test
  image: dommin/php-8.4-fpm:latest
  before_script:
    - git config --global --add safe.directory $(pwd)
    - npm ci
    - npm run build
  script:
    - npm run format
    - npm run types
    - npm run lint
  artifacts:
    paths:
      - node_modules/
      - public/build/
    expire_in: 1 hour
  only:
    - main
    - merge_requests

build_node:
  stage: build
  image: docker:24.0.5
  needs: [test_node]
  script:
    - docker buildx create --use
    - mkdir -p docker/node docker/php
    - cat "$ENV_FILE" > docker/node/.env
    - cat "$ENV_FILE" > docker/php/.env
    - |
      docker buildx build \
        --platform linux/amd64 \
        --file docker/node/Dockerfile \
        --tag $REGISTRY/$IMAGE_NAMESPACE/node:latest \
        --push \
        .
  only:
    - main

build_php:
  stage: build
  image: docker:24.0.5
  needs: [test_php, build_node]
  script:
    - docker buildx create --use
    - |
      docker buildx build \
        --platform linux/amd64 \
        --file docker/php/Dockerfile \
        --tag $REGISTRY/$IMAGE_NAMESPACE/php:latest \
        --push \
        --build-context node=docker-image://$REGISTRY/$IMAGE_NAMESPACE/node:latest \
        .
  only:
    - main

build_nginx:
  stage: build
  image: docker:24.0.5
  needs: [test_node, build_node]
  script:
    - docker buildx create --use
    - |
      docker buildx build \
        --platform linux/amd64 \
        --file docker/nginx/Dockerfile \
        --tag $REGISTRY/$IMAGE_NAMESPACE/nginx:latest \
        --push \
        --build-context node=docker-image://$REGISTRY/$IMAGE_NAMESPACE/node:latest \
        --build-arg HTPASSWD_USER=$HTPASSWD_USER \
        --build-arg HTPASSWD_PASS=$HTPASSWD_PASS \
        .
  only:
    - main

deploy_production:
  stage: deploy
  image: alpine:3.19
  needs: [build_node, build_php, build_nginx]
  before_script:
    - apk add --no-cache openssh-client
    - mkdir -p ~/.ssh
    - echo "$SSH_KEY" | tr -d '\r' > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh-keyscan -p $SSH_PORT $SSH_HOST >> ~/.ssh/known_hosts
  script:
    - cat "$ENV_FILE" > .env
    - echo >> .env
    - |
      echo "REGISTRY=$REGISTRY" >> .env
      echo "IMAGE_NAMESPACE=$IMAGE_NAMESPACE" >> .env
      echo "NODE_IMAGE_NAME=$IMAGE_NAMESPACE/node" >> .env
      echo "PHP_IMAGE_NAME=$IMAGE_NAMESPACE/php" >> .env
      echo "NGINX_IMAGE_NAME=$IMAGE_NAMESPACE/nginx" >> .env
      echo "TAG=latest" >> .env
      echo "CI_REGISTRY_USER=$CI_REGISTRY_USER" >> .env
      echo "CI_REGISTRY_PASSWORD=$CI_REGISTRY_PASSWORD" >> .env
    - scp -P $SSH_PORT docker-compose.production.yml $SSH_USER@$SSH_HOST:~/laravel/docker-compose.yml
    - scp -P $SSH_PORT .env deploy.sh $SSH_USER@$SSH_HOST:~/laravel/
    - ssh -p $SSH_PORT $SSH_USER@$SSH_HOST "cd ~/laravel && chmod +x deploy.sh && ./deploy.sh"
  only:
    - main

3. Testy: PHP i Node

Testy PHP

test_php:
  stage: test
  image: dommin/php-8.4-fpm:latest
  needs: [test_node]
  before_script:
    - composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader
  script:
    - cp .env.testing .env
    - php artisan key:generate
    - php artisan ziggy:generate
    - composer larastan
    - composer pint
    - php artisan test --env=testing
  variables:
    DB_CONNECTION: sqlite
    DB_DATABASE: ":memory:"
    SESSION_DRIVER: array
  • Testy uruchamiane są na świeżym obrazie Dockera.
  • Wykorzystywana jest baza SQLite in-memory dla szybkości.
  • Dodatkowo: statyczna analiza kodu (larastan), formatowanie (pint).

Testy Node

test_node:
  stage: test
  image: dommin/php-8.4-fpm:latest
  before_script:
    - npm ci
    - npm run build
  script:
    - npm run format
    - npm run types
    - npm run lint
  • Sprawdzanie formatowania, typów i lintowanie kodu JS/TS.
  • Build frontendu przed dalszymi krokami.

4. Budowanie obrazów Docker

Każdy komponent (Node, PHP, Nginx) budowany jest osobno, z użyciem docker buildx (wspiera multi-arch, cache, contexty):

build_node:
  stage: build
  image: docker:24.0.5
  needs: [test_node]
  script:
    - docker buildx create --use
    - docker buildx build \
        --platform linux/amd64 \
        --file docker/node/Dockerfile \
        --tag $REGISTRY/$IMAGE_NAMESPACE/node:latest \
        --push \
        .
  • Buildx pozwala na budowanie obrazów na różne architektury (np. ARM, AMD64).
  • Obrazy są pushowane do rejestru GitLab (lub DockerHub).

Analogicznie budowane są obrazy PHP i Nginx, z przekazaniem kontekstu builda (np. artefaktów z Node do PHP).


5. Automatyczne wdrożenie na serwer

Wdrożenie odbywa się przez SSH i prosty skrypt bashowy:

deploy_production:
  stage: deploy
  image: alpine:3.19
  needs: [build_node, build_php, build_nginx]
  before_script:
    - apk add --no-cache openssh-client
    - echo "$SSH_KEY" > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh-keyscan -p $SSH_PORT $SSH_HOST >> ~/.ssh/known_hosts
  script:
    - scp -P $SSH_PORT docker-compose.production.yml $SSH_USER@$SSH_HOST:~/laravel/docker-compose.yml
    - scp -P $SSH_PORT .env deploy.sh $SSH_USER@$SSH_HOST:~/laravel/
    - ssh -p $SSH_PORT $SSH_USER@$SSH_HOST "cd ~/laravel && chmod +x deploy.sh && ./deploy.sh"
  • Klucz SSH przekazywany jest jako zmienna CI/CD.
  • Pliki konfiguracyjne i skrypt wdrożeniowy są kopiowane na serwer.
  • Wdrożenie uruchamiane jest automatycznie.

6. Checklist produkcyjny i dobre praktyki

  • Zmienna ENV_FILE – upewnij się, że środowisko produkcyjne jest poprawnie przekazywane.
  • Klucze SSH – przechowuj je bezpiecznie w GitLab CI/CD Variables.
  • Rejestr obrazów – obrazy są pushowane do prywatnego rejestru (np. GitLab Registry).
  • Testy muszą przechodzić – build i deploy są zależne od testów.
  • Docker buildx – używaj dla lepszej wydajności i wsparcia multi-arch.
  • Artefakty – przekazuj tylko niezbędne pliki (np. build frontendu).
  • Automatyczne czyszczenie cache – np. php artisan config:clear po wdrożeniu.
  • Logi – monitoruj logi pipeline’u i serwera po wdrożeniu.

7. Najczęstsze problemy i debugowanie

  • Błąd połączenia z Dockerem: sprawdź, czy docker:dind działa i czy DOCKER_HOST jest ustawiony.
  • Brak uprawnień SSH: upewnij się, że klucz ma odpowiednie prawa i jest poprawnie przekazany.
  • Nieaktualne obrazy: sprawdź, czy buildx nie korzysta z cache (możesz dodać --no-cache).
  • Błędy w testach: pipeline zatrzyma się na etapie testów – sprawdź logi w GitLabie.
  • Problemy z siecią: upewnij się, że serwer produkcyjny akceptuje połączenia SSH z runnera.

8. Podsumowanie

Dzięki takiemu pipeline’owi masz w pełni zautomatyzowany, powtarzalny i bezpieczny proces wdrożenia aplikacji z testami, buildem Docker i automatycznym deployem. To podstawa nowoczesnego DevOps w każdej firmie!


Więcej porad Laravel & DevOps: Dominik Jasiński na LinkedIn

Wsparcie istniejącego systemu

Potrzebujesz pomocy z działającą aplikacją?

Pomagam firmom rozwijać działające systemy, porządkować wdrożenia i dodawać nowe funkcje bez dokładania chaosu do projektu.

Komentarze (0)
Zaloguj się, aby dodać komentarz

Musisz być zalogowany, aby dodać komentarz.

Zaloguj się

Potrzebujesz kogoś, kto weźmie odpowiedzialność za kolejny krok?

Porozmawiajmy o Twoim projekcie i określmy zakres, który ma sens dla Twoich celów.