This project provides a standardized and isolated environment for local web development based on Docker and Docker Compose. It is designed to simplify the setup and management of the multiple services required for modern web applications.
Installing and configuring a full stack of web services (web server, DBMS, language interpreter, caching, queues, etc.) directly on the developer's host machine presents several challenges:
- Dependency Conflicts: Different projects may require incompatible versions of the same software (e.g., different versions of PHP, MySQL).
- Configuration Complexity: Manually setting up each service (Nginx, PHP-FPM, PostgreSQL, etc.) requires time and specific knowledge.
- Host System Pollution: Installing numerous services and their dependencies directly into the OS can lead to difficulties in management and removal.
- Environment Discrepancy: The developer's local environment often differs from the staging or production environments, which can lead to deployment errors ("it worked on my machine").
Using Docker and Docker Compose solves these problems by containerizing each service:
- Isolation: Each service runs in its own isolated container with its dependencies, eliminating version conflicts.
- Reproducibility: The environment configuration is defined by files (
docker-compose.yml,Dockerfile), ensuring environment identity across different machines. - Ease of Management: Starting, stopping, and rebuilding the entire service stack is done using standardized Docker Compose commands.
- Clean Host System: All dependencies are installed inside containers, without affecting the main OS.
- Production Parity: Allows easy emulation of a multi-service architecture similar to the production environment.
This setup includes pre-configured images for Nginx, PHP-FPM (with selectable versions), MySQL, MariaDB, PostgreSQL, Redis, Elasticsearch, RabbitMQ, as well as web interfaces for managing databases and services.
To use this project, you need Docker Engine and the Docker Compose plugin (V2) installed. The project is compatible with the following operating systems:
- Linux
- macOS
- Windows (using WSL 2)
The following host machine ports are mapped to the Docker services:
80: Nginx (HTTP)
443: Nginx (HTTPS)
3306: MySQL
33060: MariaDB
5432: PostgreSQL
6379: Redis
9200: Elasticsearch
5601: Kibana (Web UI for Elasticsearch)
5672: RabbitMQ (AMQP port for applications)
15672: RabbitMQ Management (Web UI)
3000: PhpMyAdmin (Web UI for MySQL)
4000: PhpMyAdmin (Web UI for MariaDB)
5000: PgAdmin4 (Web UI for PostgreSQL)
Warning: For the convenience of local development, many services are configured with default or empty passwords. Never use these settings in production! It is recommended to change these passwords even for the local environment if other users or services can connect to your Docker network.
- MySQL:
- User:
root - Password: empty (allowed)
- User:
- MariaDB:
- User:
root - Password: empty (allowed)
- User:
- PostgreSQL:
- User:
postgres - Password:
root
- User:
- PgAdmin4 (Web UI for PostgreSQL):
- Email:
[email protected] - Password:
root
- Email:
- RabbitMQ:
- User:
root(set indocker-compose.yml) - Password:
root(set indocker-compose.yml) - Note: The configuration file
rabbitmq.confalso definesdefault_user = adminanddefault_pass = admin, which might be created during initialization.
- User:
- Redis:
- Password: none (empty password allowed)
When working with Docker on Linux, file permissions for volumes mounted from the host system into the container (e.g., your site's code in server/www/)
can cause issues. The web server and PHP-FPM inside the container run as the www-data user, which has its own default UID and GID.
If your user's UID/GID on the host machine differs, PHP-FPM might not have permission to write to files (logs, cache, etc.).
To solve this, the Dockerfiles for PHP-FPM (7.4, 8.0, 8.1, 8.2, 8.3, 8.4)
use UID and GID build arguments. By default, the values 1000:1000 are set, which is standard for the first user in many Linux distributions.
If your UID/GID differs (you can find them with id -u and id -g in the host terminal), you can pass your values when building the php-fpm image:
docker compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g) fpmOr during the first run with an automatic build:
# Example for PHP 8.3
PHP_VERSION=8.3 docker compose up -d --build --build-arg UID=$(id -u) --build-arg GID=$(id -g)This ensures that the www-data user inside the container has the same identifiers as your user on the host, providing correct file access permissions for the project files.
This guide outlines the steps to launch your websites in this development environment.
The web root directory (/usr/share/nginx/html inside the app container) is mapped from the server/www
directory in your project.
To add a new site:
- Create a subdirectory within
server/www/. The name of this subdirectory will be used for Nginx configuration (e.g.,server/www/myproject). - Place your site's files (e.g.,
index.php,index.html, or framework files like Laravel/WordPress) into this subdirectory (server/www/myproject/).
To access your local sites using domain names (e.g., http://myproject.local), you need to add entries to your operating
system's hosts file.
Open the hosts file with administrator privileges and add lines like the following, mapping your chosen domain names
to 127.0.0.1:
127.0.0.1 myproject.local
127.0.0.1 another-site.dev
The hosts file is typically located at:
- Windows:
C:\Windows\System32\drivers\etc\hosts - Linux (Ubuntu):
/etc/hosts - macOS:
/private/etc/hosts
Nginx configuration files for individual sites (virtual hosts) are located in the server/config/nginx/conf.d/ directory.
- For each site added in
server/www/, create a corresponding configuration file inserver/config/nginx/conf.d/. The filename should preferably match the domain name you set in thehostsfile and must end with.conf(e.g.,myproject.local.conf). - You can use the existing
default.confas a template or refer to the example configurations provided later in this README.
Key Nginx Directives:
-
server_name: Specify the domain name(s) for the site (e.g.,myproject.local www.myproject.local). -
root: Set the path to the site's document root inside the Nginx container. It must start with/usr/share/nginx/html/followed by your site's subdirectory name (e.g.,root /usr/share/nginx/html/myproject;). For frameworks like Laravel, point it to the public directory (e.g.,root /usr/share/nginx/html/laravel/public;). -
For PHP Sites: You must configure Nginx to pass
.phprequests to the PHP-FPM container. Use the followinglocationblock:location ~ \.php$ { include fastcgi_params; # Include standard FastCGI parameters # Pass request to the php-fpm service name from docker-compose.yml on port 9000 fastcgi_pass fpm:9000; fastcgi_index index.php; # Default index file for PHP # Set SCRIPT_FILENAME using the $document_root from the root directive above fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; # Hide X-Powered-By header for security fastcgi_hide_header X-Powered-By; }
Important: The fastcgi_pass value must match the service name of the PHP-FPM container defined in docker-compose.yml
(which is fpm in your setup) and the port it listens on (usually 9000).
After adding/modifying site files or Nginx configurations, restart the Nginx service for changes to take effect: docker compose restart app.
Self-signed SSL certificates are required for HTTPS access.
-
Generate Certificates (if they don't exist): If the
server/config/nginx/certs/directory is empty, generate the key and certificate files locally on your host machine:# Navigate to the certs directory cd server/config/nginx/certs/ # Generate the key and certificate openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout server.key -out server.crt \ -subj "/C=XX/ST=State/L=City/O=LocalDev/CN=localhost"
(Adjust the
-subjvalues as needed, or omit-subjfor interactive prompts.CN=localhostis usually sufficient for local development.)These files (
server.keyandserver.crt) are automatically mounted into the Nginx container bydocker-compose.yml. -
Enable SSL in Nginx Config: Edit your site's
.conffile inserver/config/nginx/conf.d/to listen on port 443 and specify the certificate paths:server { listen 80; # Optional: Redirect HTTP to HTTPS server_name myproject.local; return 301 https://$host$request_uri; } server { listen 443 ssl http2; # Enable SSL and HTTP/2 listen [::]:443 ssl http2; server_name myproject.local; # Paths inside the container ssl_certificate /etc/nginx/certs/server.crt; ssl_certificate_key /etc/nginx/certs/server.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # Your root and other location directives go here... root /usr/share/nginx/html/myproject; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; # Example for frameworks } # Include PHP location block if needed... location ~ \.php$ { # ... (fastcgi_pass fpm:9000; etc.) } }
Example Configurations: Basic configurations for WordPress, Laravel, and Next.js demonstrating HTTPS setup can be found in the tests/config/ directory.
You can also view standard Nginx configurations for specific projects, such as WordPress or Laravel.
For more details, see the Nginx documentation.
The main PHP configuration files used by the fpm service are mounted from server/config/php-fpm/conf/:
php.ini: The main PHP configuration file. Based onphp.ini-developmentby default. Modify this file to change PHP settings likememory_limit,upload_max_filesize, etc.www.conf: The configuration file for the PHP-FPM pool. Controls aspects like the number of child processes (pm.max_children).
Xdebug configuration is located in server/config/php-fpm/xdebug/xdebug.ini and is mounted separately.
After modifying these files, you may need to restart the fpm service: docker compose restart fpm. If you modify php.ini, a restart is usually required.
By default, the docker compose up -d command will launch PHP8.3. If you need a different version of PHP, you need to specify it as
an environment variable, for example, PHP_VERSION=8.1 docker compose up -d.
You can connect to the database services running in Docker using standard client tools installed on your host machine. You do not need to install the full database server locally.
Installation Examples (Ubuntu):
sudo apt update
sudo apt install mysql-client postgresql-client redis-tools
Connection Examples (from Host):
- MySQL: (Connects to port 3306 on your host, which maps to the MySQL container)
mysql -h 127.0.0.1 -P 3306 -u root
- MariaDB: (Connects to port 33060 on your host)
mysql -h 127.0.0.1 -P 33060 -u root
- PostgreSQL: (Connects to port 5432 on your host, password is 'root' as set in
docker-compose.yml)psql -h 127.0.0.1 -p 5432 -U postgres -W
- Redis:
redis-cli -h 127.0.0.1 -p 6379
The entrypoint scripts for the official MySQL, MariaDB, and PostgreSQL images automatically execute any .sql, .sql.gz,
or .sh files found in the /docker-entrypoint-initdb.d/ directory when the container is created for the first time
(i.e., when the associated named volume is empty).
This project mounts the respective server/config/[database]/databases/ directories into /docker-entrypoint-initdb.d/
inside the containers.
- Place your initial database dump files (e.g.,
my_dump.sql) in the correspondingserver/config/[database]/databases/directory on your host before the firstdocker compose up. - Ensure your SQL dumps include
CREATE DATABASE IF NOT EXISTS db_name;andUSE db_name;statements at the beginning.
Important: These scripts run only once upon initial volume creation. Subsequent container starts will use the
existing data in the volumes. To re-initialize a database (e.g., restore the initial dump), you must first remove its associated
named volume: docker compose down -v (removes all volumes for the project) or docker volume rm docker-web-server_mysql_data
(removes a specific volume).
If you need to save changes made during development, create a new dump from the running container or using your client
tools and replace the file in the databases directory before removing the volume.
- When your application code (running inside the
php-fpmcontainer) connects to a database, use the service name defined indocker-compose.ymlas the hostname (e.g.,mysql,mariadb,postgres). Docker's internal DNS will resolve these names to the correct container IP addresses within thedatabasenetwork. Do not uselocalhostor127.0.0.1in your application's database configuration (.envfile, etc.).
- When you access your application through a web browser, requests go
Browser -> Nginx (app) -> PHP-FPM (fpm). Thephp-fpmcontainer connects to the database using the service name (e.g.,mariadb). - However, if you run a command-line tool (like
php artisan migratefor Laravel) directly on your host machine (Ubuntu), that tool does not know about Docker's internal network or service names. It needs to connect to the database via the port exposed on your host machine.
In this scenario (running commands from the host), you must temporarily configure your application (e.g., in the .env file)
to use 127.0.0.1 and the corresponding host port:
Example for connecting to MariaDB from the host:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=33060 # Port mapped on the host for MariaDBRemember to revert DB_HOST back to the service name (mariadb) when running the application via the web server.
Recommendation: To avoid confusion, prefer running such commands inside the relevant container using docker exec:
# Example for Laravel migrations (uses service name 'mariadb' from .env)
docker exec fpm php artisan migrateThis project utilizes Docker Compose profiles to allow selective startup of service groups, optimizing resource usage based on your current development needs.
How it Works:
- Services in the
docker-compose.ymlfile can be assigned to one or more profiles using theprofiles:key. - When you run
docker compose up, only services without aprofiles:section (considered part of thedefaultprofile) are started by default. - To activate specific profiles (and their associated services), you use the
--profileflag.
Available Profiles:
Based on the docker-compose.yml, the following profiles are defined:
mysql-adminer: Starts PhpMyAdmin configured for themysqlservice.mariadb-adminer: Starts PhpMyAdmin configured for themariadbservice.postgresql-adminer: Starts PgAdmin4 for thepostgresqlservice.mariadb: Starts themariadbdatabase service itself.postgresql: Starts thepostgresqldatabase service itself.redis: Starts theredisservice.elasticsearch: Starts theelasticsearchandkibanaservices.rabbitmq: Starts therabbitmqservice.full: Starts all services defined in thedocker-compose.yml, including all the profiles listed above plus the default services (nginx, php-fpm, mysql).
Usage:
To start the default services plus specific profiles:
# Start default services + MariaDB + its admin interface
docker compose up -d --profile mariadb --profile mariadb-adminer
# Start default services + Elasticsearch stack
docker compose up -d --profile elasticsearchTo start everything:
docker compose up -d --profile fullNote: Services without a profile: a section (like nginx, php-fpm, mysql) will always start, regardless of the --profile
flag used, unless explicitly stopped. The depends_on directive will also automatically start required services even
if their profile is not explicitly activated.
