Skip to content

Simple docker web server for development

License

DenisLopatin/docker-web-server

Repository files navigation

Docker Nginx PHP MySQL MariaDB PostgreSQL Redis RabbitMQ Elasticsearch Kibana

PHP 7.4 PHP 8.0 PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4

Playwright Tests

Local Docker Server Logo

Local Docker Environment for Web Development

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.

Purpose and Problems Solved

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:

  1. Dependency Conflicts: Different projects may require incompatible versions of the same software (e.g., different versions of PHP, MySQL).
  2. Configuration Complexity: Manually setting up each service (Nginx, PHP-FPM, PostgreSQL, etc.) requires time and specific knowledge.
  3. Host System Pollution: Installing numerous services and their dependencies directly into the OS can lead to difficulties in management and removal.
  4. 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.

System Requirements

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)

Ports

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)

Default Credentials

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)
  • MariaDB:
    • User: root
    • Password: empty (allowed)
  • PostgreSQL:
    • User: postgres
    • Password: root
  • PgAdmin4 (Web UI for PostgreSQL):
  • RabbitMQ:
    • User: root (set in docker-compose.yml)
    • Password: root (set in docker-compose.yml)
    • Note: The configuration file rabbitmq.conf also defines default_user = admin and default_pass = admin, which might be created during initialization.
  • Redis:
    • Password: none (empty password allowed)

Setting UID/GID for PHP-FPM (Linux)

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) fpm

Or 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.


Getting Started

This guide outlines the steps to launch your websites in this development environment.

1. Add Your Site(s)

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:

  1. Create a subdirectory within server/www/. The name of this subdirectory will be used for Nginx configuration (e.g., server/www/myproject).
  2. Place your site's files (e.g., index.php, index.html, or framework files like Laravel/WordPress) into this subdirectory (server/www/myproject/).

2. Configure Your Hosts File

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

3. Configure Nginx

Nginx configuration files for individual sites (virtual hosts) are located in the server/config/nginx/conf.d/ directory.

  1. For each site added in server/www/, create a corresponding configuration file in server/config/nginx/conf.d/. The filename should preferably match the domain name you set in the hosts file and must end with .conf (e.g., myproject.local.conf).
  2. You can use the existing default.conf as 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 .php requests to the PHP-FPM container. Use the following location block:

    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.

4. Configuring SSL

Self-signed SSL certificates are required for HTTPS access.

  1. 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 -subj values as needed, or omit -subj for interactive prompts. CN=localhost is usually sufficient for local development.)

    These files (server.key and server.crt) are automatically mounted into the Nginx container by docker-compose.yml.

  2. Enable SSL in Nginx Config: Edit your site's .conf file in server/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.


Configuring PHP-FPM

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 on php.ini-development by default. Modify this file to change PHP settings like memory_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.


Configuring Databases

Connecting via Client Tools

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

Database Dumps (Initialization)

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 corresponding server/config/[database]/databases/ directory on your host before the first docker compose up.
  • Ensure your SQL dumps include CREATE DATABASE IF NOT EXISTS db_name; and USE 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.


Important Notes (Host vs. Container Access)

Database Hostnames

  • When your application code (running inside the php-fpm container) connects to a database, use the service name defined in docker-compose.yml as the hostname (e.g., mysql, mariadb, postgres). Docker's internal DNS will resolve these names to the correct container IP addresses within the database network. Do not use localhost or 127.0.0.1 in your application's database configuration (.env file, etc.).

Host Machine Commands vs. Web Access

  • When you access your application through a web browser, requests go Browser -> Nginx (app) -> PHP-FPM (fpm). The php-fpm container connects to the database using the service name (e.g., mariadb).
  • However, if you run a command-line tool (like php artisan migrate for 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 MariaDB

Remember 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 migrate

Profiles

This 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.yml file can be assigned to one or more profiles using the profiles: key.
  • When you run docker compose up, only services without a profiles: section (considered part of the default profile) are started by default.
  • To activate specific profiles (and their associated services), you use the --profile flag.

Available Profiles:

Based on the docker-compose.yml, the following profiles are defined:

  • mysql-adminer: Starts PhpMyAdmin configured for the mysql service.
  • mariadb-adminer: Starts PhpMyAdmin configured for the mariadb service.
  • postgresql-adminer: Starts PgAdmin4 for the postgresql service.
  • mariadb: Starts the mariadb database service itself.
  • postgresql: Starts the postgresql database service itself.
  • redis: Starts the redis service.
  • elasticsearch: Starts the elasticsearch and kibana services.
  • rabbitmq: Starts the rabbitmq service.
  • full: Starts all services defined in the docker-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 elasticsearch

To start everything:

docker compose up -d --profile full

Note: 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.