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 czyDOCKER_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