diff --git a/docker/dev/.env b/.env.dev.example similarity index 68% rename from docker/dev/.env rename to .env.dev.example index 8e5aaf70..a39f96f9 100644 --- a/docker/dev/.env +++ b/.env.dev.example @@ -1,3 +1,4 @@ +# Development environment configuration for Docker APP_ENV=dev APP_DEBUG=true SERVER_NAME=:80 diff --git a/docker/.env b/.env.docker.example similarity index 100% rename from docker/.env rename to .env.docker.example diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..b68effdf --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +# Application environment: dev, test, prod +APP_ENV=dev + +# Enable debug mode (true/false) +APP_DEBUG=true + +# Docker-specific settings (optional for non-Docker deployments) +# SERVER_NAME=:80 +# XDEBUG_MODE=develop +# COMPOSER_CACHE_DIR=/app/runtime/cache/composer +# APP_C3=false +# APP_HOST_PATH=/path/to/app diff --git a/.env.override.example b/.env.override.example new file mode 100644 index 00000000..296546e8 --- /dev/null +++ b/.env.override.example @@ -0,0 +1,3 @@ +# Override settings for local development (optional) +# Copy this file to .env.dev for custom local settings +# APP_HOST_PATH=/your/custom/path diff --git a/.env.prod.example b/.env.prod.example new file mode 100644 index 00000000..4872f2cd --- /dev/null +++ b/.env.prod.example @@ -0,0 +1,4 @@ +# Production environment configuration for Docker +APP_ENV=prod +APP_DEBUG=false +SERVER_NAME=:80 diff --git a/docker/test/.env b/.env.test.example similarity index 73% rename from docker/test/.env rename to .env.test.example index 7e1dd8ae..15bbcab3 100644 --- a/docker/test/.env +++ b/.env.test.example @@ -1,3 +1,4 @@ +# Test environment configuration for Docker APP_ENV=test APP_DEBUG=false APP_C3=true diff --git a/.gitignore b/.gitignore index 806d488f..f9593a2d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,11 @@ Thumbs.db # Codeception C3 c3.php + +# Environment configuration +/.env +/.env.dev +/.env.prod +/.env.test +/.env.override +/.env.docker diff --git a/docker/Dockerfile b/Dockerfile similarity index 99% rename from docker/Dockerfile rename to Dockerfile index 3c4711d1..42bf31a0 100644 --- a/docker/Dockerfile +++ b/Dockerfile @@ -50,7 +50,7 @@ USER ${USER_NAME} FROM base AS prod-builder COPY --from=composer /composer /usr/bin/composer -COPY .. /app +COPY . /app RUN --mount=type=cache,target=/tmp/cache \ composer install --no-dev --no-progress --no-interaction --classmap-authoritative && \ rm composer.lock composer.json diff --git a/Makefile b/Makefile index 49d15055..47024b1f 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ else PRIMARY_GOAL := $(firstword $(MAKECMDGOALS)) endif -include docker/.env +-include .env.docker # Current user ID and group ID except MacOS where it conflicts with Docker abilities ifeq ($(shell uname), Darwin) @@ -19,8 +19,8 @@ else endif export COMPOSE_PROJECT_NAME=${STACK_NAME} -DOCKER_COMPOSE_DEV := docker compose -f docker/compose.yml -f docker/dev/compose.yml -DOCKER_COMPOSE_TEST := docker compose -f docker/compose.yml -f docker/test/compose.yml +DOCKER_COMPOSE_DEV := docker compose -f compose.yml -f compose.dev.yml +DOCKER_COMPOSE_TEST := docker compose -f compose.yml -f compose.test.yml # # Development @@ -112,7 +112,7 @@ endif ifeq ($(PRIMARY_GOAL),prod-build) prod-build: ## PROD | Build an image - docker build --file docker/Dockerfile --target prod --pull -t ${IMAGE}:${IMAGE_TAG} . + docker build --file Dockerfile --target prod --pull -t ${IMAGE}:${IMAGE_TAG} . endif ifeq ($(PRIMARY_GOAL),prod-push) @@ -122,7 +122,7 @@ endif ifeq ($(PRIMARY_GOAL),prod-deploy) prod-deploy: ## PROD | Deploy to production - docker -H ${PROD_SSH} stack deploy --prune --detach=false --with-registry-auth -c docker/compose.yml -c docker/prod/compose.yml ${STACK_NAME} + docker -H ${PROD_SSH} stack deploy --prune --detach=false --with-registry-auth -c compose.yml -c compose.prod.yml ${STACK_NAME} endif # diff --git a/README.md b/README.md index ea34260c..0aad8765 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,27 @@ composer create-project yiisoft/app myproject cd myproject ``` +Configure the environment by creating a `.env` file from the example: + +```shell +cp .env.example .env +``` + +The `.env` file allows you to configure the application environment and other settings: +- `APP_ENV`: Application environment (`dev`, `test`, or `prod`) +- `APP_DEBUG`: Enable debug mode (`true` or `false`) + +> **Note:** The `.env` file is excluded from version control. When deployed to production without a `.env` file, +> the application will default to the `prod` environment. + To run the app: +```shell +./yii serve +``` + +Or with explicit environment override: + ```shell APP_ENV=dev ./yii serve ``` @@ -62,9 +81,18 @@ Fork the repository, clone it, then: ```shell cd myproject +cp .env.docker.example .env.docker make composer update ``` +Docker uses environment-specific configuration files from the root directory: +- `.env.docker` - Docker-specific variables (stack name, ports, image names, etc.) +- `.env.dev.example` - Development environment (used by default) +- `.env.prod.example` - Production environment +- `.env.test.example` - Test environment + +You can customize settings by creating override files (e.g., `.env.dev`) which are automatically loaded if present. + To run the app: ```shell @@ -90,13 +118,24 @@ make help The application template has the following structure: ``` +.dockerignore Docker ignore file +.env.example Example environment configuration for local development +.env.docker.example Docker-specific variables (stack name, ports, image names, etc.) +.env.dev.example Docker development environment configuration +.env.prod.example Docker production environment configuration +.env.test.example Docker test environment configuration +.env.override.example Example override file for custom local settings +Dockerfile Docker image definition +compose.yml Base Docker Compose configuration +compose.dev.yml Docker Compose configuration for development +compose.prod.yml Docker Compose configuration for production +compose.test.yml Docker Compose configuration for testing assets/ Asset bundle source files. config/ Configuration files. common/ Common configuration and DI definitions. console/ Console-specific configuration. environments/ Environment-specific configuration (dev/test/prod). web/ Web-specific configuration. -docker/ Docker-specific files. public/ Files publically accessible from the Internet. assets/ Published/compiled assets. index.php Entry script. diff --git a/docker/dev/compose.yml b/compose.dev.yml similarity index 62% rename from docker/dev/compose.yml rename to compose.dev.yml index e980164e..de0feb37 100644 --- a/docker/dev/compose.yml +++ b/compose.dev.yml @@ -1,21 +1,21 @@ services: app: build: - dockerfile: docker/Dockerfile - context: .. + dockerfile: Dockerfile + context: . target: dev args: USER_ID: ${UID} GROUP_ID: ${GID} env_file: - - path: ./dev/.env - - path: ./dev/override.env + - path: .env.dev.example + - path: .env.dev required: false ports: - "${DEV_PORT:-80}:80" volumes: - - ../:/app - - ../runtime:/app/runtime + - ./:/app + - ./runtime:/app/runtime - caddy_data:/data - caddy_config:/config tty: true diff --git a/docker/prod/compose.yml b/compose.prod.yml similarity index 92% rename from docker/prod/compose.yml rename to compose.prod.yml index 7c090f2d..e43b5453 100644 --- a/docker/prod/compose.yml +++ b/compose.prod.yml @@ -8,8 +8,8 @@ services: - caddy_data:/data - caddy_config:/config env_file: - - path: ./prod/.env - - path: ./prod/override.env + - path: .env.prod.example + - path: .env.prod required: false deploy: replicas: 2 diff --git a/docker/test/compose.yml b/compose.test.yml similarity index 58% rename from docker/test/compose.yml rename to compose.test.yml index e3d42992..4c28e4e7 100644 --- a/docker/test/compose.yml +++ b/compose.test.yml @@ -1,19 +1,19 @@ services: app: build: - dockerfile: docker/Dockerfile - context: .. + dockerfile: Dockerfile + context: . target: dev args: USER_ID: ${UID} GROUP_ID: ${GID} env_file: - - path: ./test/.env - - path: ./test/override.env + - path: .env.test.example + - path: .env.test required: false volumes: - - ../:/app - - ../runtime:/app/runtime + - ./:/app + - ./runtime:/app/runtime - caddy_data:/data - caddy_config:/config tty: true diff --git a/docker/compose.yml b/compose.yml similarity index 100% rename from docker/compose.yml rename to compose.yml diff --git a/composer.json b/composer.json index 719e4a04..920c1775 100644 --- a/composer.json +++ b/composer.json @@ -42,6 +42,7 @@ "psr/http-server-handler": "^1.0.2", "psr/log": "^3.0.2", "symfony/console": "^7.3.4", + "vlucas/phpdotenv": "^5.6", "yiisoft/aliases": "^3.1", "yiisoft/assets": "^5.1.1", "yiisoft/config": "^1.6", diff --git a/docker/dev/.gitignore b/docker/dev/.gitignore deleted file mode 100644 index bbaa3679..00000000 --- a/docker/dev/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/override.env diff --git a/docker/dev/override.env.example b/docker/dev/override.env.example deleted file mode 100644 index 3a285b74..00000000 --- a/docker/dev/override.env.example +++ /dev/null @@ -1 +0,0 @@ -APP_HOST_PATH=/projects/yiisoft/app diff --git a/docker/prod/.env b/docker/prod/.env deleted file mode 100644 index 78648a87..00000000 --- a/docker/prod/.env +++ /dev/null @@ -1,3 +0,0 @@ -APP_ENV=prod -APP_DEBUG=false -SERVER_NAME=:80 diff --git a/docker/prod/.gitignore b/docker/prod/.gitignore deleted file mode 100644 index bbaa3679..00000000 --- a/docker/prod/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/override.env diff --git a/docker/test/.gitignore b/docker/test/.gitignore deleted file mode 100644 index bbaa3679..00000000 --- a/docker/test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/override.env diff --git a/src/Environment.php b/src/Environment.php index fd834d58..da1c1512 100644 --- a/src/Environment.php +++ b/src/Environment.php @@ -80,13 +80,13 @@ private static function setEnvironment(): void { $environment = self::getRawValue('APP_ENV'); - if (!in_array($environment, self::ENVIRONMENTS, true)) { - if ($environment === null) { - $message = 'APP_ENV environment variable is empty.'; - } else { - $message = sprintf('APP_ENV="%s" environment is invalid.', $environment); - } + // Default to prod when APP_ENV is not set (production deployment scenario) + if ($environment === null) { + $environment = self::PROD; + } + if (!in_array($environment, self::ENVIRONMENTS, true)) { + $message = sprintf('APP_ENV="%s" environment is invalid.', $environment); $message .= sprintf(' Valid values are "%s".', implode('", "', self::ENVIRONMENTS)); throw new RuntimeException($message); diff --git a/src/autoload.php b/src/autoload.php index 3ed4f694..f3737f80 100644 --- a/src/autoload.php +++ b/src/autoload.php @@ -3,7 +3,18 @@ declare(strict_types=1); use App\Environment; +use Dotenv\Dotenv; require_once dirname(__DIR__) . '/vendor/autoload.php'; +// Load .env file if it exists (for non-Docker environments) +// Skip loading if APP_ENV is already set (Docker/CI optimization) +if (!isset($_ENV['APP_ENV']) && getenv('APP_ENV') === false) { + $rootDir = dirname(__DIR__); + if (file_exists($rootDir . '/.env')) { + $dotenv = Dotenv::createImmutable($rootDir); + $dotenv->safeLoad(); + } +} + Environment::prepare();