🚀 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

Komentarze (0)
Zostaw komentarz

© 2025 Wszelkie prawa zastrzeżone.