From 3aa10a779f431bc40f0e78902a58949e4bee4c14 Mon Sep 17 00:00:00 2001 From: Rahmat Nazali Salimi Date: Fri, 2 Feb 2024 17:45:36 +0700 Subject: [PATCH] Add production configuration (#14) ## Changes: - Added production template - Modify `Database.php` for custom deployment with environment variables [ba23310c624e9771959dc7c583372ff72fd305bb], closes #15. - Add custom nginx configuration for PHP-FPM & CodeIgniter --- configurations/.gitkeep | 0 configurations/production/.gitkeep | 0 configurations/production/example.yml | 55 +++++++++ configurations/production/nginx/nginx.conf | 38 +++++++ configurations/production/production.md | 124 +++++++++++++++++++++ readme.md | 6 + source/.dockerignore | 2 + source/Dockerfile | 31 ++++++ source/app/Config/Database.php | 20 ++++ 9 files changed, 276 insertions(+) delete mode 100644 configurations/.gitkeep delete mode 100644 configurations/production/.gitkeep create mode 100644 configurations/production/example.yml create mode 100644 configurations/production/nginx/nginx.conf create mode 100644 configurations/production/production.md create mode 100644 source/.dockerignore create mode 100644 source/Dockerfile diff --git a/configurations/.gitkeep b/configurations/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/configurations/production/.gitkeep b/configurations/production/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/configurations/production/example.yml b/configurations/production/example.yml new file mode 100644 index 0000000..855204b --- /dev/null +++ b/configurations/production/example.yml @@ -0,0 +1,55 @@ +# Place this at root folder of this repo + +version: '3.7' + +services: + php-fpm: + container_name: php-fpm + build: + context: ./source + environment: + - CI_ENVIRONMENT=production + - DB_DEFAULT_HOSTNAME=db-main + - DB_DEFAULT_DATABASE=main + - DB_DEFAULT_USERNAME=root + - DB_DEFAULT_PASSWORD=root + - DB_DEFAULT_DBDRIVER=MySQLi + - DB_DEFAULT_PORT=3306 + - DB_TESTS_HOSTNAME=db-test + - DB_TESTS_DATABASE=test + - DB_TESTS_USERNAME=root + - DB_TESTS_PASSWORD=root + - DB_TESTS_DBDRIVER=MySQLi + - DB_TESTS_PORT=3306 + volumes: + - source-code:/var/www + nginx: + image: nginx:latest + container_name: nginx + volumes: + - ./configurations/production/nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro + - source-code:/var/www + ports: + - "8000:80" + db-main: + image: mysql + volumes: + - main_mysql_data:/var/lib/mysql + environment: + - MYSQL_ROOT_PASSWORD=root + - MYSQL_DATABASE=main + ports: + - "3310:3306" + db-test: + image: mysql + volumes: + - test_mysql_data:/var/lib/mysql + environment: + - MYSQL_ROOT_PASSWORD=root + - MYSQL_DATABASE=test + ports: + - "3311:3306" +volumes: + main_mysql_data: + test_mysql_data: + source-code: diff --git a/configurations/production/nginx/nginx.conf b/configurations/production/nginx/nginx.conf new file mode 100644 index 0000000..8fef149 --- /dev/null +++ b/configurations/production/nginx/nginx.conf @@ -0,0 +1,38 @@ +# This file is taken from: https://www.codeigniter.com/user_guide/installation/running.html#default-conf +# Some key points: +# - enable URLSs to be accessed without "index.php" +# - Raise 404 for URLs ending with ".php" +# - Deny access to hidden files + +server { + listen 80; + listen [::]:80; + + server_name example.com; + + root /var/www/codeigniter4/public; + index index.php index.html index.htm; + + location / { + try_files $uri $uri/ /index.php$is_args$args; + } + + location ~ \.php$ { + # point to the php-fpm address + fastcgi_pass php-fpm:9000; + + # or, if nginx and php-fpm are on the same device, this can also be used + #fastcgi_pass unix:/run/php/php8.3-fpm.sock; + + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } + + error_page 404 /index.php; + + # deny access to hidden files such as .htaccess + location ~ /\. { + deny all; + } +} diff --git a/configurations/production/production.md b/configurations/production/production.md new file mode 100644 index 0000000..8dafed4 --- /dev/null +++ b/configurations/production/production.md @@ -0,0 +1,124 @@ + +# Production Configuration + +This file provides a working example of serving the source code of this repo +on a production grade environment. + +Use case: +- The source code will be served with PHP-FPM +- NGINX will be used to do reverse proxy to the PHP-FPM (other tool can also be used, as long as it's compatible with FastCGI Protocol). + +For the sake of simplicity, the architecture is detailed at [example.yml](example.yml). +It can then be converted to a vps-style serving, docker swarm, or kubernetes, as needed. + +## Preparing the CodeIgniter 4 Image + +### Building the image + +[Dockerfile](../../source/Dockerfile) is supplied inside `/source`, so image can be built like so: + +``` +cd source +docker build -t my-application:1 +``` + +The source code will be copied inside the official [PHP-FPM Image](https://hub.docker.com/_/php). + +### Passing environment variables + +Currently, there are 13 environment variables that can be adjusted: + +| Variable name | Description | +|---------------------|----------------------------------------------------------| +| CI_ENVIRONMENT | Environment for the site (`development` or `production`) | +| DB_DEFAULT_HOSTNAME | Host name for the default database | +| DB_DEFAULT_DATABASE | Name for the default database | +| DB_DEFAULT_USERNAME | Username for the default database | +| DB_DEFAULT_PASSWORD | Password for the default database | +| DB_DEFAULT_DBDRIVER | DB Driver for the default database (`SQLite3`, `MySQLi`) | +| DB_DEFAULT_PORT | Port for the default database | +| DB_TESTS_HOSTNAME | Host name for the test database | +| DB_TESTS_DATABASE | Name for the test database | +| DB_TESTS_USERNAME | Username for the test database | +| DB_TESTS_PASSWORD | Password for the test database | +| DB_TESTS_DBDRIVER | DB Driver for the test database (`SQLite3`, `MySQLi`) | +| DB_TESTS_PORT | Port for the default database | + +The `/source/.env` file will be ignored when creating the image. +Please use the appropriate way from the stack you've picked to pass an environment variables. + +For example, in docker compose you would do it like this: + +``` +services: + fpm: + environment: + - CI_ENVIRONMENT=production + - DB_DEFAULT_HOSTNAME=db-main + - DB_DEFAULT_DATABASE=main + - DB_DEFAULT_USERNAME=root + - DB_DEFAULT_PASSWORD=root + - DB_DEFAULT_DBDRIVER=MySQLi + - DB_DEFAULT_PORT=3306 + - DB_TESTS_HOSTNAME=db-test + - DB_TESTS_DATABASE=test + - DB_TESTS_USERNAME=root + - DB_TESTS_PASSWORD=root + - DB_TESTS_DBDRIVER=MySQLi + - DB_TESTS_PORT=3306 +``` + +In Kubernetes, insert them inside the specification and mix its usage with `ConfigMap` or `Secret` as necessary. + +## Preparing the NGINX + +The [nginx.conf](nginx/nginx.conf) is provided as a baseline for serving CodeIgniter and PHP in general. +You may want to modify it as you need: + +- Change `server_name` from `example.com` to the domain you intended to use. +- Confirm that `root` points to the `/source/public` folder, where CodeIgniter should start form. + If NGINX and PHP-FPM are not being installed on the same device, + you may want to make sure NGINX can access the source code inside the PHP-FPM. +- Confirm that `fastcgi_pass` points to the PHP-FPM's address. + +## Additional Notes + +### PHP-FPM related notes + +We can make the image work without any configuration change. +But if needed, the PHP-FPM main configuration file is located at `/usr/local/etc/php-fpm.conf`. +In that file, it will include all other files located at `/usr/local/etc/php-fpm.d/`. + +The most related configuration file is probably the `www.conf`, +in which it already set these by default, and you may want to change this according to the needs. + +``` +user = www-data +group = www-data +listen = 127.0.0.1:9000 + +;listen.owner = www-data +;listen.group = www-data +;listen.mode = 0660 + +;security.limit_extensions = .php .php3 .php4 .php5 .php7 + +;env[HOSTNAME] = $HOSTNAME +;env[PATH] = /usr/local/bin:/usr/bin:/bin +;env[TMP] = /tmp +;env[TMPDIR] = /tmp +;env[TEMP] = /tmp +``` + +In any case that the default configuration path is invalid, +you may need to get it by: + +``` +docker exec -ti fpm php-fpm -tt +``` + +### Useful reference + +- https://github.com/atsanna/codeigniter4-docker +- https://github.com/LERUfic/codeigniter-docker +- https://github.com/firmanJS/Dockerize-Codeigniter-With-docker-compose diff --git a/readme.md b/readme.md index 041871b..ddda760 100644 --- a/readme.md +++ b/readme.md @@ -112,5 +112,11 @@ composer run-script test - Use the provided [Debug Toolbar](https://codeigniter4.github.io/userguide/tutorial/index.html#debug-toolbar) - Error logs will be printed on `writable/logs` +## Production Configuration + +This repo offers a working configuration for serving the source code on a production environment +with PHP-FPM and NGINX. +More about this can be found at [production.md](configurations/production/production.md). + ## Useful external resources - 25 minutes of [basic MVC in CodeIgniter 4](https://youtu.be/c8zHxE-mN4c?si=pNoCCJwCjGoRfYQp) diff --git a/source/.dockerignore b/source/.dockerignore new file mode 100644 index 0000000..e4dbee9 --- /dev/null +++ b/source/.dockerignore @@ -0,0 +1,2 @@ +.env +./vendor diff --git a/source/Dockerfile b/source/Dockerfile new file mode 100644 index 0000000..cbef0b6 --- /dev/null +++ b/source/Dockerfile @@ -0,0 +1,31 @@ +FROM php:8.3.2-fpm + +WORKDIR /var/www + +RUN apt-get update && \ + apt-get install -y \ + git \ + zip \ + curl \ + libicu-dev + +RUN docker-php-ext-install pdo_mysql +RUN docker-php-ext-install mysqli +RUN docker-php-ext-install intl + +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +COPY composer.json codeigniter4/composer.json +COPY composer.lock codeigniter4/composer.lock +RUN cd codeigniter4 && composer install + +COPY . codeigniter4 + +RUN chown -R www-data:www-data codeigniter4/ +RUN chmod -R 755 codeigniter4/writable/ + +RUN apt-get clean && rm -rf /var/lib/apt/lists/* + +EXPOSE 9000 + +CMD ["php-fpm"] diff --git a/source/app/Config/Database.php b/source/app/Config/Database.php index e2450ec..bca57f0 100644 --- a/source/app/Config/Database.php +++ b/source/app/Config/Database.php @@ -81,5 +81,25 @@ public function __construct() if (ENVIRONMENT === 'testing') { $this->defaultGroup = 'tests'; } + + // assign the default database configuration from environment variables (if given) + if (getenv('DB_DEFAULT_HOSTNAME')){ + $this->default['hostname'] = getenv('DB_DEFAULT_HOSTNAME') ?? $this->default['hostname']; + $this->default['username'] = getenv('DB_DEFAULT_USERNAME') ?? $this->default['username']; + $this->default['password'] = getenv('DB_DEFAULT_PASSWORD') ?? $this->default['password']; + $this->default['database'] = getenv('DB_DEFAULT_DATABASE') ?? $this->default['database']; + $this->default['DBDriver'] = getenv('DB_DEFAULT_DBDRIVER') ?? $this->default['DBDriver']; + $this->default['port'] = getenv('DB_DEFAULT_PORT') ?? $this->default['port']; + } + + // assign the test database configuration from environment variables (if given) + if (getenv('DB_TESTS_HOSTNAME')){ + $this->tests['hostname'] = getenv('DB_TESTS_HOSTNAME') ?? $this->tests['hostname']; + $this->tests['username'] = getenv('DB_TESTS_USERNAME') ?? $this->tests['username']; + $this->tests['password'] = getenv('DB_TESTS_PASSWORD') ?? $this->tests['password']; + $this->tests['database'] = getenv('DB_TESTS_DATABASE') ?? $this->tests['database']; + $this->tests['DBDriver'] = getenv('DB_TESTS_DBDRIVER') ?? $this->tests['DBDriver']; + $this->tests['port'] = getenv('DB_TESTS_PORT') ?? $this->tests['port']; + } } }