diff --git a/.env.prod.example b/.env.prod.example new file mode 100644 index 0000000..b937baf --- /dev/null +++ b/.env.prod.example @@ -0,0 +1,80 @@ +APP_NAME=Bowly +APP_ENV=production +APP_KEY= +APP_DEBUG=false +APP_URL=https://bemeal.leonmorival.com +APP_MOBILE_SCHEME=bowly +APP_LOCALE=fr +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=fr_FR + +LOG_CHANNEL=stack +LOG_STACK=single +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=warning + +DB_CONNECTION=pgsql +DB_HOST=postgres +DB_PORT=5432 +DB_DATABASE= +DB_USERNAME= +DB_PASSWORD= + +SESSION_DRIVER=database +SESSION_LIFETIME=120 +SESSION_ENCRYPT=true +SESSION_SECURE_COOKIE=true +SESSION_SAME_SITE=lax + +CACHE_STORE=redis +QUEUE_CONNECTION=redis + +REDIS_CLIENT=predis +REDIS_HOST=redis +REDIS_PASSWORD=null +REDIS_PORT=6379 + +FILESYSTEM_DISK=s3 +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=eu-north-1 +AWS_BUCKET=bowli +AWS_URL= +AWS_ENDPOINT= +AWS_USE_PATH_STYLE_ENDPOINT=false + +MAIL_MAILER=resend +MAIL_FROM_ADDRESS= +MAIL_FROM_NAME="${APP_NAME}" +RESEND_API_KEY= + +SCOUT_DRIVER=meilisearch +MEILISEARCH_HOST=http://meilisearch:7700 +MEILISEARCH_KEY= + +GEMINI_API_KEY= + +AI_MEAL_USER_NAME="Daily Meal AI" +AI_MEAL_USER_EMAIL=ai@daily-meal.local +AI_MEAL_IMAGE_PATH=meal-posts/ai-generated +AI_MEAL_IMAGE_QUALITY=medium +AI_MEAL_TEXT_TIMEOUT=90 +AI_MEAL_IMAGE_TIMEOUT=120 + +EXPO_ACCESS_TOKEN= + +STRAVA_CLIENT_ID= +STRAVA_CLIENT_SECRET= +STRAVA_SCOPE=read,activity:read + +STRIPE_KEY= +STRIPE_SECRET= +STRIPE_WEBHOOK_SECRET= +CASHIER_CURRENCY=eur + +HEALTH_SECRET_TOKEN= +HORIZON_HEARTBEAT_URL= +SCHEDULE_HEARTBEAT_URL= + +IMAGE_TAG=latest +ADMINER_PORT=8080 diff --git a/.gitea/workflows/cd.yml b/.gitea/workflows/cd.yml index 67687c1..01dba91 100644 --- a/.gitea/workflows/cd.yml +++ b/.gitea/workflows/cd.yml @@ -35,12 +35,12 @@ jobs: cd /data/stacks/daily-meal-api echo "Pull nouvelle image" - docker compose pull + docker compose -f docker-compose.prod.yml pull echo "Redémarrage" - docker compose up -d + docker compose -f docker-compose.prod.yml up -d echo "Migration Laravel" - docker compose exec -T app php artisan migrate --force + docker compose -f docker-compose.prod.yml exec -T app php artisan migrate --force EOF diff --git a/Dockerfile b/Dockerfile index 08bbcc7..25a1e3b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,61 +1,43 @@ -# --- Étape de Build (Composer) --- -FROM composer:2 AS composer-bin +FROM dunglas/frankenphp:php8.4-alpine -FROM dunglas/frankenphp:1.3-php8.4-alpine AS composer-build +WORKDIR /app -WORKDIR /var/www/html - -COPY --from=composer-bin /usr/bin/composer /usr/bin/composer - -RUN install-php-extensions intl zip bcmath pdo_pgsql pcntl redis - -COPY . . - -RUN composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader - -# --- Étape de Build (Node) --- -FROM node:20-alpine AS node-build - -WORKDIR /var/www/html - -COPY package*.json ./ -RUN npm ci - -COPY --from=composer-build /var/www/html/vendor ./vendor -COPY . . -RUN npm run build - -# --- Étape Finale (Production) --- -FROM dunglas/frankenphp:1.3-php8.4-alpine - -# On installe les extensions pour le fonctionnement de l'app en prod -RUN apk add --no-cache \ - libpq-dev \ - libpng-dev \ - libjpeg-turbo-dev \ - freetype-dev \ - icu-dev RUN install-php-extensions \ - intl \ pdo_pgsql \ - gd \ - zip \ opcache \ pcntl \ - redis + zip \ + intl -WORKDIR /var/www/html -COPY --from=composer-build /var/www/html /var/www/html -COPY --from=node-build /var/www/html/public/build /var/www/html/public/build +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer -# Permissions (crucial pour Laravel) -RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache -ENV APP_ENV=production -ENV APP_RUNTIME=Laravel\FrankenPHP\Runtime +COPY composer.json composer.lock ./ -HEALTHCHECK NONE +RUN composer install \ + --no-dev \ + --no-interaction \ + --prefer-dist \ + --optimize-autoloader \ + --no-scripts -CMD ["sh", "-c", "php artisan migrate --force && php artisan storage:link && php artisan optimize && php artisan view:cache && php artisan event:cache && frankenphp run --config /etc/caddy/Caddyfile"] + +COPY . . + + +RUN composer dump-autoload --optimize + + + +RUN chown -R www-data:www-data storage bootstrap/cache + + +COPY docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + + +EXPOSE 8002 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 7cc2b59..7605076 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,17 +1,78 @@ -name: bemeal +name: daily-meal-api + +x-app-image: &app-image git.leonmorival.xyz/leonm/daily-meal-api:${IMAGE_TAG:-latest} + +x-app-environment: &app-environment + APP_ENV: production + APP_DEBUG: "false" + APP_RUNTIME: "Laravel\\FrankenPHP\\Runtime" + SERVER_NAME: ":80" + DB_CONNECTION: pgsql + DB_HOST: postgres + DB_PORT: "5432" + REDIS_HOST: redis + REDIS_CLIENT: predis + QUEUE_CONNECTION: redis + SCOUT_DRIVER: meilisearch + MEILISEARCH_HOST: http://meilisearch:7700 + +x-app-volumes: &app-volumes + - storage-data:/var/www/html/storage/app + - storage-public-data:/var/www/html/storage/app/public + +x-app-service: &app-service + image: *app-image + restart: unless-stopped + env_file: + - .env + environment: *app-environment + volumes: *app-volumes + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_started + meilisearch: + condition: service_started + networks: + - internal + services: - pgsql: + app: + <<: *app-service + container_name: daily-meal-api + networks: + - proxy + - internal + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1/api/health >/dev/null 2>&1"] + start_period: 30s + interval: 10s + timeout: 5s + retries: 12 + + horizon: + <<: *app-service + container_name: daily-meal-api-horizon + command: ["php", "artisan", "horizon"] + + scheduler: + <<: *app-service + container_name: daily-meal-api-scheduler + command: ["php", "artisan", "schedule:work"] + + postgres: image: postgres:15-alpine - container_name: bemeal-pgsql + container_name: daily-meal-api-postgres restart: unless-stopped environment: POSTGRES_DB: "${DB_DATABASE:?DB_DATABASE is required}" POSTGRES_USER: "${DB_USERNAME:?DB_USERNAME is required}" POSTGRES_PASSWORD: "${DB_PASSWORD:?DB_PASSWORD is required}" volumes: - - pgsql-data:/var/lib/postgresql/data + - postgres-data:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -p 5432"] + test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -p 5432 -U $$POSTGRES_USER -d $$POSTGRES_DB"] start_period: 30s interval: 5s timeout: 5s @@ -21,36 +82,58 @@ services: redis: image: redis:alpine - container_name: bemeal-redis + container_name: daily-meal-api-redis restart: unless-stopped volumes: - redis-data:/data networks: - internal + meilisearch: + image: getmeili/meilisearch:latest + container_name: daily-meal-api-meilisearch + restart: unless-stopped + environment: + MEILI_NO_ANALYTICS: "true" + MEILI_MASTER_KEY: "${MEILISEARCH_KEY:?MEILISEARCH_KEY is required}" + volumes: + - meilisearch-data:/meili_data + networks: + - internal + adminer: image: adminer:latest - container_name: bemeal-adminer + container_name: daily-meal-api-adminer restart: unless-stopped profiles: - debug ports: - "127.0.0.1:${ADMINER_PORT:-8080}:8080" environment: - ADMINER_DEFAULT_SERVER: bemeal-pgsql + ADMINER_DEFAULT_SERVER: postgres networks: - internal - volumes: - pgsql-data: + postgres-data: external: true name: bemeal_pgsql_data redis-data: name: bemeal_redis_data + meilisearch-data: + name: bemeal_meilisearch_data + + storage-data: + name: bemeal_storage_data + + storage-public-data: + name: bemeal_storage_public_data + networks: - internal: + proxy: external: true - name: bemeal_internal + + internal: + driver: bridge