From 6eae91754097a903457ccf5fd11fd924092b8dca Mon Sep 17 00:00:00 2001 From: soheil khaledabadi Date: Fri, 5 Jun 2026 17:19:39 +0330 Subject: [PATCH] feat(docker): add Docker configuration and production environment setup --- .dockerignore | 28 ++++++++++ .env.production.example | 68 ++++++++++++++++++++++++ Dockerfile | 86 ++++++++++++++++++++++++++++++ README.md | 108 +++++++++++++++++++++++++++++++++++++- docker-compose.yml | 100 +++++++++++++++++++++++++++++++++++ docker/entrypoint.sh | 33 ++++++++++++ docker/nginx/default.conf | 49 +++++++++++++++++ docker/php/opcache.ini | 8 +++ docker/php/php.ini | 12 +++++ resources/js/app.js | 1 + 10 files changed, 492 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 .env.production.example create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 docker/entrypoint.sh create mode 100644 docker/nginx/default.conf create mode 100644 docker/php/opcache.ini create mode 100644 docker/php/php.ini diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cd1f67e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,28 @@ +.git +.github +.idea +.vscode + +.env +.env.* +!.env.example +!.env.production.example + +node_modules +vendor +npm-debug.log +yarn-error.log + +storage/logs/* +storage/framework/cache/data/* +storage/framework/sessions/* +storage/framework/views/* +bootstrap/cache/*.php + +tests +coverage +.phpunit.cache + +Dockerfile +docker-compose*.yml +README.md diff --git a/.env.production.example b/.env.production.example new file mode 100644 index 0000000..bba4bfe --- /dev/null +++ b/.env.production.example @@ -0,0 +1,68 @@ +APP_NAME=Hoshpoint +APP_ENV=production +APP_KEY= +APP_DEBUG=false +APP_URL=https://example.com +HTTP_PORT=8080 + +APP_LOCALE=fa +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=fa_IR + +APP_MAINTENANCE_DRIVER=file +BCRYPT_ROUNDS=12 + +LOG_CHANNEL=stderr +LOG_STACK=stderr +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=info + +DB_CONNECTION=mariadb +DB_HOST=mariadb +DB_PORT=3306 +DB_DATABASE=hoshpoint_backend +DB_USERNAME=hoshpoint +DB_PASSWORD=change-me + +MARIADB_DATABASE=hoshpoint_backend +MARIADB_USER=hoshpoint +MARIADB_PASSWORD=change-me +MARIADB_ROOT_PASSWORD=change-root-password + +SESSION_DRIVER=database +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_PATH=/ +SESSION_DOMAIN=null +SESSION_SECURE_COOKIE=true +SESSION_HTTP_ONLY=true +SESSION_SAME_SITE=lax + +BROADCAST_CONNECTION=log +FILESYSTEM_DISK=local +QUEUE_CONNECTION=database +CACHE_STORE=database + +MEMCACHED_HOST=memcached + +REDIS_CLIENT=phpredis +REDIS_HOST=redis +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=log +MAIL_SCHEME=null +MAIL_HOST=127.0.0.1 +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_FROM_ADDRESS=hello@example.com +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +VITE_APP_NAME="${APP_NAME}" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..07d4986 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,86 @@ +# syntax=docker/dockerfile:1.7 + +ARG PHP_VERSION=8.4 + +FROM composer:2 AS composer + +FROM node:24-alpine AS assets +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY resources ./resources +COPY public ./public +COPY vite.config.js ./ +RUN npm run build + +FROM php:${PHP_VERSION}-fpm-bookworm AS app +WORKDIR /var/www/html + +ENV COMPOSER_ALLOW_SUPERUSER=1 + +ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/install-php-extensions + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + curl \ + default-mysql-client \ + unzip \ + && install-php-extensions \ + bcmath \ + dom \ + intl \ + mbstring \ + opcache \ + pcntl \ + pdo_mysql \ + xml \ + xmlreader \ + zip \ + && apt-get purge -y --auto-remove \ + && rm -rf /var/lib/apt/lists/* + +COPY docker/php/php.ini /usr/local/etc/php/conf.d/99-production.ini +COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/99-opcache.ini +COPY --from=composer /usr/bin/composer /usr/bin/composer + +COPY composer.json composer.lock ./ +RUN composer install \ + --no-dev \ + --no-autoloader \ + --no-scripts \ + --prefer-dist \ + --no-interaction \ + && composer check-platform-reqs --no-dev + +COPY . . +COPY --from=assets /app/public/build ./public/build + +RUN composer dump-autoload --no-dev --optimize --no-scripts \ + && php artisan package:discover --ansi \ + && mkdir -p \ + storage/app/public \ + storage/framework/cache/data \ + storage/framework/sessions \ + storage/framework/views \ + storage/logs \ + bootstrap/cache \ + && rm -rf public/storage \ + && ln -s ../storage/app/public public/storage \ + && chown -R www-data:www-data storage bootstrap/cache + +COPY docker/entrypoint.sh /usr/local/bin/docker-entrypoint +RUN chmod +x /usr/local/bin/docker-entrypoint + +EXPOSE 9000 + +ENTRYPOINT ["docker-entrypoint"] +CMD ["php-fpm"] + +FROM nginx:1.27-alpine AS nginx + +COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf +COPY --from=app /var/www/html/public /var/www/html/public + +EXPOSE 80 diff --git a/README.md b/README.md index bf52c7b..f23dcab 100644 --- a/README.md +++ b/README.md @@ -1 +1,107 @@ -### Hoshpoint \ No newline at end of file +### Hoshpoint + +## اجرای پروژه با Docker در Production + +ابتدا Docker Desktop را اجرا کنید و فایل env پروداکشن را بسازید: + +```bash +cp .env.production.example .env.production +php artisan key:generate --show +``` + +خروجی را کپی کنید و داخل `.env.production` بگذارید (خط `APP_KEY` نباید خالی بماند): + +```env +APP_KEY=base64:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx= +``` + +سپس `APP_URL`، `HTTP_PORT`، `DB_PASSWORD`، `MARIADB_PASSWORD` و `MARIADB_ROOT_PASSWORD` را هم تغییر دهید. مقدار پیش‌فرض `HTTP_PORT=8080` است. + +اگر قبلاً بدون `APP_KEY` کانتینرها را بالا آورده‌اید، بعد از پر کردن کلید این‌ها را اجرا کنید: + +```bash +docker compose --env-file .env.production exec app php artisan config:clear +docker compose --env-file .env.production restart app queue scheduler +``` + +برای build کردن imageها: + +```bash +docker compose --env-file .env.production build +``` + +اول دیتابیس را بالا بیاورید: + +```bash +docker compose --env-file .env.production up -d mariadb +``` + +بعد migrationها را اجرا کنید: + +```bash +docker compose --env-file .env.production run --rm app php artisan migrate --force --seed +``` + +حالا همه سرویس‌ها را بالا بیاورید: + +```bash +docker compose --env-file .env.production up -d +``` + +سایت از طریق پورت تنظیم‌شده در `HTTP_PORT` در دسترس است. مقدار پیش‌فرض: + +```text +http://localhost:8080 +``` + +برای تغییر پورت، مقدار زیر را در `.env.production` عوض کنید و سرویس `nginx` را دوباره بالا بیاورید: + +```env +HTTP_PORT=8081 +``` + +```bash +docker compose --env-file .env.production up -d --force-recreate nginx +``` + +برای دیدن وضعیت سرویس‌ها: + +```bash +docker compose --env-file .env.production ps +``` + +برای دیدن لاگ‌ها: + +```bash +docker compose --env-file .env.production logs -f +``` + +برای اجرای دستورهای Artisan داخل کانتینر: + +```bash +docker compose --env-file .env.production exec app php artisan about +``` + +برای متوقف کردن سرویس‌ها: + +```bash +docker compose --env-file .env.production down +``` + +برای حذف کامل دیتای دیتابیس و volumeها، فقط وقتی مطمئن هستید: + +```bash +docker compose --env-file .env.production down -v +``` + +## خطای `No application encryption key has been specified` + +یعنی `APP_KEY` در `.env.production` خالی است یا بعد از تغییر env، cache قدیمی مانده. + +1. مقدار `APP_KEY` را در `.env.production` تنظیم کنید. +2. cache را پاک و سرویس‌ها را restart کنید: + +```bash +docker compose --env-file .env.production exec app php artisan config:clear +docker compose --env-file .env.production restart app queue scheduler +``` \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..59ff9bd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,100 @@ +services: + app: + build: + context: . + target: app + image: hoshpoint-backend-app:production + restart: unless-stopped + env_file: + - ${APP_ENV_FILE:-.env.production} + environment: + APP_ENV: production + APP_DEBUG: "false" + LOG_CHANNEL: stderr + DB_HOST: mariadb + DB_PORT: 3306 + depends_on: + mariadb: + condition: service_healthy + volumes: + - app-storage:/var/www/html/storage + networks: + - hoshpoint + + nginx: + build: + context: . + target: nginx + image: hoshpoint-backend-nginx:production + restart: unless-stopped + depends_on: + - app + ports: + - "${HTTP_PORT:-8080}:80" + volumes: + - app-storage:/var/www/html/storage:ro + networks: + - hoshpoint + + queue: + image: hoshpoint-backend-app:production + restart: unless-stopped + env_file: + - ${APP_ENV_FILE:-.env.production} + environment: + APP_ENV: production + APP_DEBUG: "false" + LOG_CHANNEL: stderr + DB_HOST: mariadb + DB_PORT: 3306 + command: php artisan queue:work database --sleep=3 --tries=3 --timeout=90 + depends_on: + mariadb: + condition: service_healthy + volumes: + - app-storage:/var/www/html/storage + networks: + - hoshpoint + + scheduler: + image: hoshpoint-backend-app:production + restart: unless-stopped + env_file: + - ${APP_ENV_FILE:-.env.production} + environment: + APP_ENV: production + APP_DEBUG: "false" + LOG_CHANNEL: stderr + DB_HOST: mariadb + DB_PORT: 3306 + command: sh -c "while true; do php artisan schedule:run --no-interaction; sleep 60; done" + depends_on: + mariadb: + condition: service_healthy + volumes: + - app-storage:/var/www/html/storage + networks: + - hoshpoint + + mariadb: + image: mariadb:11.4 + restart: unless-stopped + env_file: + - ${APP_ENV_FILE:-.env.production} + volumes: + - mariadb-data:/var/lib/mysql + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - hoshpoint + +volumes: + app-storage: + mariadb-data: + +networks: + hoshpoint: + driver: bridge diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..4dc0dda --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env sh +set -eu + +mkdir -p \ + storage/app/public \ + storage/framework/cache/data \ + storage/framework/sessions \ + storage/framework/views \ + storage/logs \ + bootstrap/cache + +if [ ! -L public/storage ]; then + rm -rf public/storage + ln -s ../storage/app/public public/storage +fi + +if [ "$(id -u)" = "0" ]; then + chown -R www-data:www-data storage bootstrap/cache +fi + +if [ -z "${APP_KEY:-}" ]; then + echo "ERROR: APP_KEY is empty. Set it in .env.production, then restart containers." >&2 + echo " php artisan key:generate --show" >&2 + exit 1 +fi + +if [ "${APP_ENV:-production}" = "production" ]; then + php artisan config:cache --no-interaction + php artisan event:cache --no-interaction + php artisan view:cache --no-interaction +fi + +exec "$@" diff --git a/docker/nginx/default.conf b/docker/nginx/default.conf new file mode 100644 index 0000000..3a1d67c --- /dev/null +++ b/docker/nginx/default.conf @@ -0,0 +1,49 @@ +server { + listen 80; + server_name _; + + root /var/www/html/public; + index index.php; + + charset utf-8; + client_max_body_size 20M; + + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location = /favicon.ico { + access_log off; + log_not_found off; + } + + location = /robots.txt { + access_log off; + log_not_found off; + } + + location ~* \.(?:css|js|jpg|jpeg|gif|png|svg|ico|webp|woff|woff2|ttf)$ { + expires 30d; + add_header Cache-Control "public, immutable"; + access_log off; + try_files $uri /index.php?$query_string; + } + + location ~ \.php$ { + try_files $uri =404; + fastcgi_pass app:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + fastcgi_param DOCUMENT_ROOT $realpath_root; + include fastcgi_params; + fastcgi_hide_header X-Powered-By; + } + + location ~ /\.(?!well-known).* { + deny all; + } +} diff --git a/docker/php/opcache.ini b/docker/php/opcache.ini new file mode 100644 index 0000000..ba7a0bc --- /dev/null +++ b/docker/php/opcache.ini @@ -0,0 +1,8 @@ +opcache.enable = 1 +opcache.enable_cli = 0 +opcache.memory_consumption = 192 +opcache.interned_strings_buffer = 16 +opcache.max_accelerated_files = 20000 +opcache.validate_timestamps = 0 +opcache.save_comments = 1 +opcache.fast_shutdown = 1 diff --git a/docker/php/php.ini b/docker/php/php.ini new file mode 100644 index 0000000..5175aeb --- /dev/null +++ b/docker/php/php.ini @@ -0,0 +1,12 @@ +expose_php = Off +memory_limit = 256M +max_execution_time = 60 +max_input_time = 60 +upload_max_filesize = 20M +post_max_size = 20M +variables_order = EGPCS +realpath_cache_size = 4096K +realpath_cache_ttl = 600 +log_errors = On +error_log = /proc/self/fd/2 +date.timezone = UTC diff --git a/resources/js/app.js b/resources/js/app.js index 8337712..18a4459 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1 +1,2 @@ +// JavaScript entry point required by vite.config.js. //