diff --git a/app/.dockerignore b/app/.dockerignore new file mode 100644 index 0000000..1b87658 --- /dev/null +++ b/app/.dockerignore @@ -0,0 +1,12 @@ +.git +.gitignore +.env +.env.* +node_modules +vendor +tests +storage/logs/* +storage/framework/cache/* +storage/framework/sessions/* +storage/framework/views/* +coverage diff --git a/app/Dockerfile b/app/Dockerfile new file mode 100644 index 0000000..c064a96 --- /dev/null +++ b/app/Dockerfile @@ -0,0 +1,51 @@ +# syntax=docker/dockerfile:1.7 + +FROM node:22-alpine AS frontend +WORKDIR /var/www/html + +COPY package.json ./ +RUN npm install + +COPY resources ./resources +COPY public ./public +COPY vite.config.js ./ +RUN npm run build + +FROM composer:2 AS vendor +WORKDIR /var/www/html + +COPY composer.json composer.lock ./ +RUN composer install \ + --no-dev \ + --no-interaction \ + --no-progress \ + --prefer-dist \ + --optimize-autoloader + +FROM php:8.2-apache +WORKDIR /var/www/html + +RUN apt-get update && apt-get install -y \ + libzip-dev \ + libicu-dev \ + unzip \ + curl \ + default-mysql-client \ + && docker-php-ext-install pdo_mysql bcmath exif intl zip \ + && a2enmod rewrite headers expires \ + && rm -rf /var/lib/apt/lists/* + +COPY . . +COPY --from=vendor /var/www/html/vendor ./vendor +COPY --from=frontend /var/www/html/public/build ./public/build +COPY docker/apache-vhost.conf /etc/apache2/sites-available/000-default.conf +COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh + +RUN chmod +x /usr/local/bin/entrypoint.sh \ + && chown -R www-data:www-data storage bootstrap/cache \ + && chmod -R ug+rwx storage bootstrap/cache + +EXPOSE 80 + +ENTRYPOINT ["entrypoint.sh"] +CMD ["apache2-foreground"] diff --git a/app/docker/apache-vhost.conf b/app/docker/apache-vhost.conf new file mode 100644 index 0000000..0b9aa3e --- /dev/null +++ b/app/docker/apache-vhost.conf @@ -0,0 +1,12 @@ + + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html/public + + + AllowOverride All + Require all granted + + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + diff --git a/app/docker/entrypoint.sh b/app/docker/entrypoint.sh new file mode 100644 index 0000000..a24d299 --- /dev/null +++ b/app/docker/entrypoint.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env sh +set -eu + +cd /var/www/html + +php artisan config:clear >/dev/null 2>&1 || true + +if [ "${DB_CONNECTION:-}" = "mysql" ] && [ "${WAIT_FOR_DB:-true}" = "true" ]; then + echo "Waiting for MySQL at ${DB_HOST:-mysql}:${DB_PORT:-3306}..." + i=0 + while [ "$i" -lt 60 ]; do + if php -r 'try { new PDO("mysql:host=".getenv("DB_HOST").";port=".getenv("DB_PORT").";dbname=".getenv("DB_DATABASE"), getenv("DB_USERNAME"), getenv("DB_PASSWORD"), [PDO::ATTR_TIMEOUT => 3]); exit(0);} catch (Throwable $e) { exit(1);}'; then + echo "MySQL is reachable." + break + fi + i=$((i + 1)) + sleep 2 + done +fi + +if [ "${RUN_MIGRATIONS:-true}" = "true" ]; then + php artisan migrate --force +fi + +php artisan storage:link >/dev/null 2>&1 || true + +exec "$@" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9e3e481 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,66 @@ +services: + app: + build: + context: ./app + dockerfile: Dockerfile + restart: unless-stopped + depends_on: + mysql: + condition: service_healthy + ports: + - "${APP_PORT:-8000}:80" + environment: + APP_NAME: ${APP_NAME:-Dewemoji} + APP_ENV: ${APP_ENV:-production} + APP_DEBUG: ${APP_DEBUG:-false} + APP_URL: ${APP_URL:-http://localhost:8000} + APP_KEY: ${APP_KEY:-} + + DB_CONNECTION: mysql + DB_HOST: mysql + DB_PORT: 3306 + DB_DATABASE: ${DB_DATABASE:-dewemoji} + DB_USERNAME: ${DB_USERNAME:-dewemoji} + DB_PASSWORD: ${DB_PASSWORD:-changeme} + + SESSION_DRIVER: ${SESSION_DRIVER:-file} + CACHE_STORE: ${CACHE_STORE:-file} + QUEUE_CONNECTION: ${QUEUE_CONNECTION:-sync} + + DEWEMOJI_BILLING_MODE: ${DEWEMOJI_BILLING_MODE:-sandbox} + DEWEMOJI_GUMROAD_ENABLED: ${DEWEMOJI_GUMROAD_ENABLED:-false} + DEWEMOJI_GUMROAD_PRODUCT_IDS: ${DEWEMOJI_GUMROAD_PRODUCT_IDS:-} + DEWEMOJI_GUMROAD_TEST_KEYS: ${DEWEMOJI_GUMROAD_TEST_KEYS:-} + DEWEMOJI_MAYAR_ENABLED: ${DEWEMOJI_MAYAR_ENABLED:-false} + DEWEMOJI_MAYAR_API_BASE: ${DEWEMOJI_MAYAR_API_BASE:-} + DEWEMOJI_MAYAR_ENDPOINT_VERIFY: ${DEWEMOJI_MAYAR_ENDPOINT_VERIFY:-/v1/license/verify} + DEWEMOJI_MAYAR_API_KEY: ${DEWEMOJI_MAYAR_API_KEY:-} + DEWEMOJI_MAYAR_SECRET_KEY: ${DEWEMOJI_MAYAR_SECRET_KEY:-} + + WAIT_FOR_DB: ${WAIT_FOR_DB:-true} + RUN_MIGRATIONS: ${RUN_MIGRATIONS:-true} + volumes: + - app_storage:/var/www/html/storage + + mysql: + image: mysql:8.0 + restart: unless-stopped + environment: + MYSQL_DATABASE: ${DB_DATABASE:-dewemoji} + MYSQL_USER: ${DB_USERNAME:-dewemoji} + MYSQL_PASSWORD: ${DB_PASSWORD:-changeme} + MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-rootchangeme} + ports: + - "${MYSQL_PORT:-3306}:3306" + volumes: + - mysql_data:/var/lib/mysql + healthcheck: + test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u$$MYSQL_USER -p$$MYSQL_PASSWORD --silent"] + interval: 10s + timeout: 5s + retries: 15 + start_period: 20s + +volumes: + app_storage: + mysql_data: diff --git a/rebuild-progress.md b/rebuild-progress.md index 7380de9..f6b3f8d 100644 --- a/rebuild-progress.md +++ b/rebuild-progress.md @@ -149,6 +149,16 @@ - [ ] Run parity QA on tone handling, insert/copy behavior, and API limits. - [ ] Prepare migration + rollback checklist (DNS/host switch, redirects, monitoring). +### Phase 10 - Deployment hardening (Coolify/ops) +- [x] Define single-stack deployment template (app + mysql) in the same destination/network. +- [x] Add startup sequence for server deployment: + - wait for DB readiness + - run migrations automatically (idempotent) + - start web server +- [ ] Keep local/dev simple with SQLite profile; keep staging/prod profile on MySQL. +- [ ] Add deployment runbook with minimum required env vars and health verification steps. +- [ ] Add post-deploy smoke checks (`/`, `/v1/health`, `/v1/emojis`, `/robots.txt`, `/sitemap.xml`). + ## Recent implementation update - Added new API endpoints in rebuild: @@ -179,6 +189,10 @@ - `/sitemap.xml` route generated from dataset - meta/OG/Twitter fields in shared layout - JSON-LD blocks on `/pricing`, `/support`, `/api-docs`, and emoji detail page +- Added Docker deployment baseline for one-pack deployment: + - `docker-compose.yml` (Laravel app + MySQL 8 with healthcheck + persistent volumes) + - `app/Dockerfile` (build Vite assets + Composer deps, serve via Apache) + - `app/docker/entrypoint.sh` (DB wait + auto-migrate + startup) ## Live audit highlights (reference)