{"id":13566516,"url":"https://github.com/mjstealey/wordpress-nginx-docker","last_synced_at":"2025-04-12T15:37:14.142Z","repository":{"id":41844804,"uuid":"109909483","full_name":"mjstealey/wordpress-nginx-docker","owner":"mjstealey","description":"WordPress FPM / MySQL / Nginx - Orchestrated with Docker Compose","archived":false,"fork":false,"pushed_at":"2023-07-17T14:30:06.000Z","size":5104,"stargazers_count":734,"open_issues_count":12,"forks_count":330,"subscribers_count":23,"default_branch":"master","last_synced_at":"2024-10-11T18:13:42.958Z","etag":null,"topics":["docker","docker-compose","letsencrypt","mysql","nginx","wordpress"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mjstealey.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2017-11-08T00:59:42.000Z","updated_at":"2024-10-04T12:45:11.000Z","dependencies_parsed_at":"2024-04-07T23:42:08.965Z","dependency_job_id":null,"html_url":"https://github.com/mjstealey/wordpress-nginx-docker","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjstealey%2Fwordpress-nginx-docker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjstealey%2Fwordpress-nginx-docker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjstealey%2Fwordpress-nginx-docker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjstealey%2Fwordpress-nginx-docker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mjstealey","download_url":"https://codeload.github.com/mjstealey/wordpress-nginx-docker/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248590504,"owners_count":21129829,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","docker-compose","letsencrypt","mysql","nginx","wordpress"],"created_at":"2024-08-01T13:02:11.135Z","updated_at":"2025-04-12T15:37:14.100Z","avatar_url":"https://github.com/mjstealey.png","language":null,"readme":"# WordPress (FPM Edition) - Docker\n\nNotes on deploying a single site [WordPress FPM Edition](https://hub.docker.com/_/wordpress/) instance as a docker deployment orchestrated by Docker Compose.\n\n- Use the FPM version of WordPress (v5-fpm)\n- Use MySQL as the database (v8)\n- Use Nginx as the web server (v1)\n- Use Adminer as the database management tool (v4)\n- Include self-signed SSL certificate ([Let's Encrypt localhost](https://letsencrypt.org/docs/certificates-for-localhost/) format)\n\n**DISCLAIMER: The code herein may not be up to date nor compliant with the most recent package and/or security notices. The frequency at which this code is reviewed and updated is based solely on the lifecycle of the project for which it was written to support, and is not actively maintained outside of that scope. Use at your own risk.**\n\n## Table of contents\n\n- [Overview](#overview)\n    - [Host requirements](#reqts)\n- [Configuration](#config)\n- [Deploy](#deploy)\n- [Adminer](#adminer)\n- [Teardown](#teardown)\n- [References](#references)\n- [Notes](#notes)\n\n## \u003ca name=\"overview\"\u003e\u003c/a\u003eOverview\n\nWordPress is a free and open source blogging tool and a content management system (CMS) based on PHP and MySQL, which runs on a web hosting service. Features include a plugin architecture and a template system.\n\nThis variant contains PHP-FPM, which is a FastCGI implementation for PHP. \n\n- See the [PHP-FPM website](https://php-fpm.org/) for more information about PHP-FPM.\n- In order to use this image variant, some kind of reverse proxy (such as NGINX, Apache, or other tool which speaks the FastCGI protocol) will be required.\n\n### \u003ca name=\"reqts\"\u003e\u003c/a\u003eHost requirements\n\nBoth Docker and Docker Compose are required on the host to run this code\n\n- Install Docker Engine: [https://docs.docker.com/engine/install/](https://docs.docker.com/engine/install/)\n- Install Docker Compose: [https://docs.docker.com/compose/install/](https://docs.docker.com/compose/install/)\n\n## \u003ca name=\"config\"\u003e\u003c/a\u003eConfiguration\n\nCopy the `env.template` file as `.env` and populate according to your environment\n\n```ini\n# docker-compose environment file\n#\n# When you set the same environment variable in multiple files,\n# here’s the priority used by Compose to choose which value to use:\n#\n#  1. Compose file\n#  2. Shell environment variables\n#  3. Environment file\n#  4. Dockerfile\n#  5. Variable is not defined\n\n# Wordpress Settings\nexport WORDPRESS_LOCAL_HOME=./wordpress\nexport WORDPRESS_UPLOADS_CONFIG=./config/uploads.ini\nexport WORDPRESS_DB_HOST=database:3306\nexport WORDPRESS_DB_NAME=wordpress\nexport WORDPRESS_DB_USER=wordpress\nexport WORDPRESS_DB_PASSWORD=password123!\n\n# MySQL Settings\nexport MYSQL_LOCAL_HOME=./dbdata\nexport MYSQL_DATABASE=${WORDPRESS_DB_NAME}\nexport MYSQL_USER=${WORDPRESS_DB_USER}\nexport MYSQL_PASSWORD=${WORDPRESS_DB_PASSWORD}\nexport MYSQL_ROOT_PASSWORD=rootpassword123!\n\n# Nginx Settings\nexport NGINX_CONF=./nginx/default.conf\nexport NGINX_SSL_CERTS=./ssl\nexport NGINX_LOGS=./logs/nginx\n\n# User Settings\n# TBD\n```\n\nModify `nginx/default.conf` and replace `$host` and `8443` with your **Domain Name** and exposed **HTTPS Port** throughout the file\n\n```conf\n# default.conf\n# redirect to HTTPS\nserver {\n    listen 80;\n    listen [::]:80;\n    server_name $host;\n    location / {\n        # update port as needed for host mapped https\n        rewrite ^ https://$host:8443$request_uri? permanent;\n    }\n}\n\nserver {\n    listen 443 ssl http2;\n    listen [::]:443 ssl http2;\n    server_name $host;\n    index index.php index.html index.htm;\n    root /var/www/html;\n    server_tokens off;\n    client_max_body_size 75M;\n\n    # update ssl files as required by your deployment\n    ssl_certificate /etc/ssl/fullchain.pem;\n    ssl_certificate_key /etc/ssl/privkey.pem;\n\n    # logging\n    access_log /var/log/nginx/wordpress.access.log;\n    error_log /var/log/nginx/wordpress.error.log;\n\n    # some security headers ( optional )\n    add_header X-Frame-Options \"SAMEORIGIN\" always;\n    add_header X-XSS-Protection \"1; mode=block\" always;\n    add_header X-Content-Type-Options \"nosniff\" always;\n    add_header Referrer-Policy \"no-referrer-when-downgrade\" always;\n    add_header Content-Security-Policy \"default-src * data: 'unsafe-eval' 'unsafe-inline'\" always;\n\n    location / {\n        try_files $uri $uri/ /index.php$is_args$args;\n    }\n\n    location ~ \\.php$ {\n        try_files $uri = 404;\n        fastcgi_split_path_info ^(.+\\.php)(/.+)$;\n        fastcgi_pass wordpress:9000;\n        fastcgi_index index.php;\n        include fastcgi_params;\n        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n        fastcgi_param PATH_INFO $fastcgi_path_info;\n    }\n\n    location ~ /\\.ht {\n        deny all;\n    }\n\n    location = /favicon.ico {\n        log_not_found off; access_log off;\n    }\n\n    location = /favicon.svg {\n        log_not_found off; access_log off;\n    }\n\n    location = /robots.txt {\n        log_not_found off; access_log off; allow all;\n    }\n\n    location ~* \\.(css|gif|ico|jpeg|jpg|js|png)$ {\n        expires max;\n        log_not_found off;\n    }\n}\n```\n\nModify the `config/uploads.ini` file if the preset values are not to your liking (defaults shown below)\n\n```ini\nfile_uploads = On\nmemory_limit = 256M\nupload_max_filesize = 75M\npost_max_size = 75M\nmax_execution_time = 600\n```\n\nIncluded `uploads.ini` file allows for **Maximum upload file size: 75 MB**\n\n![](./imgs/WP-media-filesize.png)\n\n## \u003ca name=\"deploy\"\u003e\u003c/a\u003eDeploy\n\nOnce configured the containers can be brought up using Docker Compose\n\n1. Set the environment variables and pull the images\n\n    ```console\n    source .env\n    docker-compose pull\n    ```\n\n2. Bring up the Database and allow it a moment to create the WordPress user and database tables\n\n    ```console\n    docker-compose up -d database\n    ```\n    \n    You will know it's ready when you see something like this in the docker logs\n    \n    ```console\n    $ docker-compose logs database\n    wp-database  | 2022-01-28 13:40:18+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.28-1debian10 started.\n    wp-database  | 2022-01-28 13:40:18+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'\n    wp-database  | 2022-01-28 13:40:18+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.28-1debian10 started.\n    wp-database  | 2022-01-28 13:40:18+00:00 [Note] [Entrypoint]: Initializing database files\n    ...\n    wp-database  | 2022-01-28 13:40:28+00:00 [Note] [Entrypoint]: Creating database wordpress\n    wp-database  | 2022-01-28 13:40:28+00:00 [Note] [Entrypoint]: Creating user wordpress\n    wp-database  | 2022-01-28 13:40:28+00:00 [Note] [Entrypoint]: Giving user wordpress access to schema wordpress\n    wp-database  |\n    wp-database  | 2022-01-28 13:40:28+00:00 [Note] [Entrypoint]: Stopping temporary server\n    wp-database  | 2022-01-28T13:40:29.002886Z 13 [System] [MY-013172] [Server] Received SHUTDOWN from user root. Shutting down mysqld (Version: 8.0.28).\n    wp-database  | 2022-01-28T13:40:30.226306Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.0.28)  MySQL Community Server - GPL.\n    wp-database  | 2022-01-28 13:40:31+00:00 [Note] [Entrypoint]: Temporary server stopped\n    wp-database  |\n    wp-database  | 2022-01-28 13:40:31+00:00 [Note] [Entrypoint]: MySQL init process done. Ready for start up.\n    wp-database  |\n    ...\n    wp-database  | 2022-01-28T13:40:32.061642Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Bind-address: '::' port: 33060, socket: /var/run/mysqld/mysqlx.sock\n    wp-database  | 2022-01-28T13:40:32.061790Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.28'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server - GPL.\n    ```\n\n3. Bring up the WordPress and Nginx containers\n\n    ```console\n    docker-compose up -d wordpress nginx\n    ```\n    \n    After a few moments the containers should be observed as running\n    \n    ```console\n    $ docker-compose ps\n    NAME                COMMAND                  SERVICE             STATUS              PORTS\n    wp-database         \"docker-entrypoint.s…\"   database            running             33060/tcp\n    wp-nginx            \"/docker-entrypoint.…\"   nginx               running             0.0.0.0:8080-\u003e80/tcp, 0.0.0.0:8443-\u003e443/tcp\n    wp-wordpress        \"docker-entrypoint.s…\"   wordpress           running             9000/tcp\n    ```\n\nThe WordPress application can be reached at the designated host and port (e.g. [https://127.0.0.1:8443]()).\n\n- **NOTE**: you will likely have to acknowledge the security risk if using the included self-signed certificate.\n\n![](./imgs/WP-first-run.png) \n\nComplete the initial WordPress installation process, and when completed you should see something similar to this.\n\n![](./imgs/WP-dashboard.png)\n![](./imgs/WP-view-site.png)\n\n## \u003ca name=\"adminer\"\u003e\u003c/a\u003eAdminer\n\nAn Adminer configuration has been included in the `docker-compose.yml` definition file, but commented out. Since it bypasses Nginx it is recommended to only use Adminer as needed, and to not let it run continuously.\n\nExpose Adminer by uncommenting the `adminer` section of the `docker-compose.yml` file\n\n```yaml\n...\n  # adminer - bring up only as needed - bypasses nginx\n  adminer:\n    # default port 8080\n    image: adminer:4\n    container_name: wp-adminer\n    restart: unless-stopped\n    networks:\n      - wordpress\n    depends_on:\n      - database\n    ports:\n      - \"9000:8080\"\n...\n```\n\nAnd run the `adminer` container\n\n```console\n$ docker-compose up -d adminer\n[+] Running 2/2\n ⠿ Container wp-database  Running                                                                                                      0.0s\n ⠿ Container wp-adminer   Started                                                                                                      0.9s\n```\n\nSince Adminer is bypassing our Nginx configuration it will be running over HTTP in plain text on port 9000 (e.g. [http://127.0.0.1:9000/]())\n\n![](./imgs/WP-adminer.png)\n\nEnter the connection information for your Database and you should see something similar to image below.\n\nExample connection information:\n\n- System: **MySQL**\n- Server: **database**\n- Username: **wordpress**\n- Password: **password123!**\n- Database: **wordpress**\n\n    **NOTE**: Since `adminer` is defined in the same docker-compose file as the MySQL Database it will \"understand\" the **Server** reference as **database**, otherwise this would need to be a formal URL reference\n\n![](./imgs/WP-adminer-connected.png)\n\nWhen finished, stop and remove the `adminer` container.\n\n```console\n$ docker-compose stop adminer\n[+] Running 1/1\n ⠿ Container wp-adminer  Stopped                                                                                                       0.1s\n$ docker-compose rm -fv adminer\nGoing to remove wp-adminer\n[+] Running 1/0\n ⠿ Container wp-adminer  Removed                                                                                                       0.0s\n```\n\n## \u003ca name=\"teardown\"\u003e\u003c/a\u003eTeardown\n\nFor a complete teardown all containers must be stopped and removed along with the volumes and network that were created for the application containers\n\nCommands\n\n```console\ndocker-compose stop\ndocker-compose rm -fv\ndocker-network rm wp-wordpress\n# removal calls may require sudo rights depending on file permissions\nrm -rf ./wordpress\nrm -rf ./dbdata\nrm -rf ./logs\n```\n\nExpected output\n\n```console\n$ docker-compose stop\n[+] Running 3/3\n ⠿ Container wp-nginx      Stopped                                                                                                     0.3s\n ⠿ Container wp-wordpress  Stopped                                                                                                     0.2s\n ⠿ Container wp-database   Stopped                                                                                                     0.8s\n$ docker-compose rm -fv\nGoing to remove wp-nginx, wp-wordpress, wp-database\n[+] Running 3/0\n ⠿ Container wp-nginx      Removed                                                                                                     0.0s\n ⠿ Container wp-database   Removed                                                                                                     0.0s\n ⠿ Container wp-wordpress  Removed                                                                                                     0.0s\n$ docker network rm wp-wordpress\nwp-wordpress\n$ rm -rf ./wordpress\n$ rm -rf ./dbdata\n$ rm -rf ./logs\n```\n\n## \u003ca name=\"references\"\u003e\u003c/a\u003eReferences\n\n- WordPress Docker images: [https://hub.docker.com/_/wordpress/](https://hub.docker.com/_/wordpress/)\n- MySQL Docker images: [https://hub.docker.com/_/mysql](https://hub.docker.com/_/mysql)\n- Nginx Docker images: [https://hub.docker.com/_/nginx/](https://hub.docker.com/_/nginx/)\n- Adminer Docker images: [https://hub.docker.com/_/adminer](https://hub.docker.com/_/adminer)\n\n---\n\n## \u003ca name=\"notes\"\u003e\u003c/a\u003eNotes\n\nGeneral information regarding standard Docker deployment of WordPress for reference purposes\n\n### Let's Encrypt SSL Certificate\n\nUse: [https://github.com/RENCI-NRIG/ez-letsencrypt](https://github.com/RENCI-NRIG/ez-letsencrypt) - A shell script to obtain and renew [Let's Encrypt](https://letsencrypt.org/) certificates using Certbot's `--webroot` method of [certificate issuance](https://certbot.eff.org/docs/using.html#webroot).\n\n### Error establishing database connection\n\nThis can happen when the `wordpress` container attempts to reach the `database` container prior to it being ready for a connection.\n\n![](./imgs/WP-database-connection.png)\n\nThis will sometimes resolve itself once the database fully spins up, but generally it's advised to start the database first and ensure it's created all of its user and wordpress tables and then start the WordPress service.\n\n### Port Mapping\n\nNeither the **wordpress** container nor the **database** container have publicly exposed ports. They are running on the host using a docker defined network which provides the containers with access to each others ports, but not from the host.\n\nIf you wish to expose the ports to the host, you'd need to alter the stanzas for each in the `docker-compose.yml` file.\n\nFor the `database` stanza, add\n\n```\n    ports:\n      - \"3306:3306\"\n```\n\nFor the `wordpress` stanza, add\n\n```\n    ports:\n      - \"9000:9000\"\n```\n","funding_links":[],"categories":["Others"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmjstealey%2Fwordpress-nginx-docker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmjstealey%2Fwordpress-nginx-docker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmjstealey%2Fwordpress-nginx-docker/lists"}