feat: docker docmpose
Laravel CI-CD / Tests Unitaires (push) Successful in 21s Details
Laravel CI-CD / Build & Push Docker (push) Successful in 40s Details
Laravel CI-CD / Deploy Blue/Green (Caddy routing) (push) Failing after 23s Details

This commit is contained in:
Leon Morival 2026-05-19 15:52:29 +02:00
parent dadd4e04b1
commit e984ec04b5
2 changed files with 74 additions and 81 deletions

View File

@ -63,7 +63,7 @@ jobs:
# DEPLOY BLUE / GREEN (CADDY GÈRE LE ROUTING) # DEPLOY BLUE / GREEN (CADDY GÈRE LE ROUTING)
# ========================= # =========================
deploy: deploy:
name: Deploy Blue/Green name: Deploy Blue/Green (Caddy routing)
needs: build needs: build
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -78,98 +78,66 @@ jobs:
username: ${{ secrets.SSH_USER }} username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }} key: ${{ secrets.SSH_PRIVATE_KEY }}
source: docker-compose.prod.yml source: docker-compose.prod.yml
target: ${{ secrets.DEPLOY_PATH || '/opt/bemeal' }} target: /opt/bemeal
- name: Deploy via SSH - name: Deploy via SSH
uses: appleboy/ssh-action@v1.0.3 uses: appleboy/ssh-action@v1.0.3
env: env:
IMAGE_TAG: ${{ github.sha }} IMAGE_TAG: ${{ github.sha }}
REGISTRY: ${{ env.REGISTRY }} REGISTRY: gitea.leonmorival.com
IMAGE_NAME: ${{ env.IMAGE_NAME }} IMAGE_NAME: daily_meal/bemeal-api
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
with: with:
host: ${{ secrets.SSH_HOST }} host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }} username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }} key: ${{ secrets.SSH_PRIVATE_KEY }}
envs: REGISTRY,IMAGE_NAME,IMAGE_TAG,DEPLOY_PATH envs: IMAGE_TAG,REGISTRY,IMAGE_NAME
script: | script: |
set -eu set -eu
APP_DIR="${DEPLOY_PATH:-/opt/bemeal}" cd /opt/bemeal
COMPOSE_FILE="docker-compose.prod.yml"
cd "$APP_DIR"
COMPOSE="docker compose --env-file .env.prod -f $COMPOSE_FILE"
COMPOSE="docker compose --env-file .env.prod -f docker-compose.prod.yml"
IMAGE="$REGISTRY/$IMAGE_NAME:$IMAGE_TAG" IMAGE="$REGISTRY/$IMAGE_NAME:$IMAGE_TAG"
echo "Pull image: $IMAGE"
docker pull "$IMAGE" docker pull "$IMAGE"
echo "Start shared services" echo "Deploy shared infra"
$COMPOSE up -d pgsql redis meilisearch $COMPOSE up -d pgsql redis meilisearch
# ========================= # detect active
# BLUE / GREEN DETECTION
# =========================
ACTIVE="" ACTIVE=""
if docker ps | grep -q bemeal-api-blue && \
if docker ps --format '{{.Names}}' | grep -q "bemeal-api-blue"; then [ "$(docker inspect -f '{{.State.Health.Status}}' bemeal-api-blue 2>/dev/null || true)" = "healthy" ]; then
if [ "$(docker inspect -f '{{.State.Health.Status}}' bemeal-api-blue 2>/dev/null || true)" = "healthy" ]; then ACTIVE=blue
ACTIVE="blue"
fi
fi fi
if docker ps --format '{{.Names}}' | grep -q "bemeal-api-green"; then if docker ps | grep -q bemeal-api-green && \
if [ "$(docker inspect -f '{{.State.Health.Status}}' bemeal-api-green 2>/dev/null || true)" = "healthy" ]; then [ "$(docker inspect -f '{{.State.Health.Status}}' bemeal-api-green 2>/dev/null || true)" = "healthy" ]; then
ACTIVE="green" ACTIVE=green
fi
fi fi
if [ "$ACTIVE" = "blue" ]; then if [ "$ACTIVE" = "blue" ]; then
TARGET="green" TARGET=green
else else
TARGET="blue" TARGET=blue
fi fi
TARGET_SERVICE="bemeal-api-$TARGET" TARGET_SERVICE="bemeal-api-$TARGET"
echo "Deploying to $TARGET_SERVICE" echo "Deploy $TARGET_SERVICE"
IMAGE="$REGISTRY/$IMAGE_NAME:$IMAGE_TAG" \ IMAGE="$IMAGE" $COMPOSE up -d --no-deps --force-recreate "$TARGET_SERVICE"
$COMPOSE up -d --force-recreate --no-deps "$TARGET_SERVICE"
# =========================
# HEALTHCHECK
# =========================
echo "Waiting for healthcheck..."
# wait health
for i in $(seq 1 60); do for i in $(seq 1 60); do
STATUS="$(docker inspect -f '{{.State.Health.Status}}' "$TARGET_SERVICE" 2>/dev/null || true)" STATUS=$(docker inspect -f '{{.State.Health.Status}}' "$TARGET_SERVICE" || true)
[ "$STATUS" = "healthy" ] && break
if [ "$STATUS" = "healthy" ]; then
break
fi
sleep 2 sleep 2
done done
if [ "$STATUS" != "healthy" ]; then [ "$STATUS" != "healthy" ] && exit 1
echo "Healthcheck failed"
docker logs --tail=200 "$TARGET_SERVICE" || true
exit 1
fi
docker exec "$TARGET_SERVICE" wget -qO- http://127.0.0.1/api/health >/dev/null || exit 1 # 👉 SWITCH ULTRA SIMPLE
echo "bemeal-api-$TARGET" > /opt/bemeal/caddy/active
echo "Deployment successful" docker exec caddy caddy reload
# =========================
# CLEAN OLD CONTAINER
# =========================
if [ -n "$ACTIVE" ] && [ "$ACTIVE" != "$TARGET" ]; then
echo "Stopping old $ACTIVE"
$COMPOSE stop "bemeal-api-$ACTIVE" || true
$COMPOSE rm -f "bemeal-api-$ACTIVE" || true
fi

View File

@ -1,7 +1,13 @@
name: bemeal name: bemeal
# =========================
# IMAGE
# =========================
x-app-image: &app-image gitea.leonmorival.com/daily_meal/bemeal-api:${IMAGE_TAG:-latest} x-app-image: &app-image gitea.leonmorival.com/daily_meal/bemeal-api:${IMAGE_TAG:-latest}
# =========================
# ENV
# =========================
x-app-environment: &app-environment x-app-environment: &app-environment
APP_LOCALE: "fr" APP_LOCALE: "fr"
APP_ENV: "production" APP_ENV: "production"
@ -14,33 +20,30 @@ x-app-environment: &app-environment
FILESYSTEM_DISK: "public" FILESYSTEM_DISK: "public"
DB_CONNECTION: "pgsql" DB_CONNECTION: "pgsql"
DB_HOST: "pgsql" DB_HOST: "bemeal-pgsql"
DB_PORT: "5432" DB_PORT: "5432"
DB_DATABASE: "${DB_DATABASE:?DB_DATABASE is required}" DB_DATABASE: "${DB_DATABASE:?DB_DATABASE is required}"
DB_USERNAME: "${DB_USERNAME:?DB_USERNAME is required}" DB_USERNAME: "${DB_USERNAME:?DB_USERNAME is required}"
DB_PASSWORD: "${DB_PASSWORD:?DB_PASSWORD is required}" DB_PASSWORD: "${DB_PASSWORD:?DB_PASSWORD is required}"
GEMINI_API_KEY: "${GEMINI_API_KEY:?GEMINI_API_KEY is required}" REDIS_HOST: "bemeal-redis"
AI_MEAL_USER_NAME: "${AI_MEAL_USER_NAME:-Lolo AI}"
AI_MEAL_USER_EMAIL: "${AI_MEAL_USER_EMAIL:-loloai@maily.com}"
AI_MEAL_IMAGE_PATH: "${AI_MEAL_IMAGE_PATH:-meal-posts/ai-generated}"
AI_MEAL_IMAGE_QUALITY: "${AI_MEAL_IMAGE_QUALITY:-medium}"
AI_MEAL_TEXT_TIMEOUT: "${AI_MEAL_TEXT_TIMEOUT:-90}"
AI_MEAL_IMAGE_TIMEOUT: "${AI_MEAL_IMAGE_TIMEOUT:-120}"
REDIS_HOST: "redis"
REDIS_CLIENT: "predis" REDIS_CLIENT: "predis"
QUEUE_CONNECTION: "redis" QUEUE_CONNECTION: "redis"
SCOUT_DRIVER: "meilisearch" SCOUT_DRIVER: "meilisearch"
MEILISEARCH_HOST: "http://meilisearch:7700" MEILISEARCH_HOST: "http://bemeal-meilisearch:7700"
MEILISEARCH_KEY: "${MEILISEARCH_KEY:?MEILISEARCH_KEY is required}" MEILISEARCH_KEY: "${MEILISEARCH_KEY:?MEILISEARCH_KEY is required}"
# =========================
# VOLUMES
# =========================
x-app-volumes: &app-volumes x-app-volumes: &app-volumes
- storage-data:/var/www/html/storage/app - storage-data:/var/www/html/storage/app
- storage-public-data:/var/www/html/storage/app/public - storage-public-data:/var/www/html/storage/app/public
# =========================
# DEPENDS
# =========================
x-app-depends-on: &app-depends-on x-app-depends-on: &app-depends-on
pgsql: pgsql:
condition: service_healthy condition: service_healthy
@ -49,6 +52,9 @@ x-app-depends-on: &app-depends-on
meilisearch: meilisearch:
condition: service_started condition: service_started
# =========================
# HEALTHCHECK
# =========================
x-app-healthcheck: &app-healthcheck x-app-healthcheck: &app-healthcheck
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1/api/health >/dev/null 2>&1"] test: ["CMD-SHELL", "wget -qO- http://127.0.0.1/api/health >/dev/null 2>&1"]
start_period: 30s start_period: 30s
@ -56,6 +62,9 @@ x-app-healthcheck: &app-healthcheck
timeout: 5s timeout: 5s
retries: 12 retries: 12
# =========================
# APP BASE
# =========================
x-app-service: &app-service x-app-service: &app-service
image: *app-image image: *app-image
restart: unless-stopped restart: unless-stopped
@ -67,7 +76,11 @@ x-app-service: &app-service
- public - public
stop_grace_period: 30s stop_grace_period: 30s
# =========================
# SERVICES
# =========================
services: services:
# ========================= # =========================
# BLUE # BLUE
# ========================= # =========================
@ -75,10 +88,6 @@ services:
<<: *app-service <<: *app-service
container_name: bemeal-api-blue container_name: bemeal-api-blue
healthcheck: *app-healthcheck healthcheck: *app-healthcheck
networks:
public:
aliases:
- bemeal-app
# ========================= # =========================
# GREEN # GREEN
@ -87,10 +96,6 @@ services:
<<: *app-service <<: *app-service
container_name: bemeal-api-green container_name: bemeal-api-green
healthcheck: *app-healthcheck healthcheck: *app-healthcheck
networks:
public:
aliases:
- bemeal-app
# ========================= # =========================
# DATABASE # DATABASE
@ -114,6 +119,9 @@ services:
networks: networks:
- internal - internal
# =========================
# REDIS
# =========================
redis: redis:
image: redis:alpine image: redis:alpine
container_name: bemeal-redis container_name: bemeal-redis
@ -123,6 +131,9 @@ services:
networks: networks:
- internal - internal
# =========================
# HORIZON
# =========================
horizon: horizon:
image: *app-image image: *app-image
container_name: bemeal-horizon container_name: bemeal-horizon
@ -134,10 +145,13 @@ services:
condition: service_started condition: service_started
environment: *app-environment environment: *app-environment
volumes: *app-volumes volumes: *app-volumes
command: "php artisan horizon" command: php artisan horizon
networks: networks:
- internal - internal
# =========================
# SCHEDULER
# =========================
scheduler: scheduler:
image: *app-image image: *app-image
container_name: bemeal-scheduler container_name: bemeal-scheduler
@ -149,19 +163,25 @@ services:
condition: service_started condition: service_started
environment: *app-environment environment: *app-environment
volumes: *app-volumes volumes: *app-volumes
command: "php artisan schedule:work" command: php artisan schedule:work
networks: networks:
- internal - internal
# =========================
# ADMINER
# =========================
adminer: adminer:
image: adminer:latest image: adminer:latest
container_name: bemeal-adminer container_name: bemeal-adminer
restart: unless-stopped restart: unless-stopped
environment: environment:
ADMINER_DEFAULT_SERVER: pgsql ADMINER_DEFAULT_SERVER: bemeal-pgsql
networks: networks:
- internal - internal
# =========================
# MEILISEARCH
# =========================
meilisearch: meilisearch:
image: getmeili/meilisearch:latest image: getmeili/meilisearch:latest
container_name: bemeal-meilisearch container_name: bemeal-meilisearch
@ -181,12 +201,16 @@ volumes:
pgsql-data: pgsql-data:
external: true external: true
name: bemeal_pgsql_data name: bemeal_pgsql_data
redis-data: redis-data:
name: bemeal_redis_data name: bemeal_redis_data
storage-data: storage-data:
name: bemeal_storage_data name: bemeal_storage_data
storage-public-data: storage-public-data:
name: bemeal_storage_public_data name: bemeal_storage_public_data
meilisearch-data: meilisearch-data:
name: bemeal_meilisearch_data name: bemeal_meilisearch_data
@ -195,6 +219,7 @@ volumes:
# ========================= # =========================
networks: networks:
internal: internal:
name: bemeal_internal
driver: bridge driver: bridge
public: public: