├── .gitignore ├── LICENSE ├── README.md ├── config └── uploads.ini ├── docker-compose.yml ├── docker-compose.yml.defaults ├── env.template ├── imgs ├── WP-adminer-connected.png ├── WP-adminer.png ├── WP-dashboard.png ├── WP-database-connection.png ├── WP-first-run.png ├── WP-media-filesize.png └── WP-view-site.png ├── nginx └── default.conf └── ssl ├── README.md ├── chain.pem ├── fullchain.pem └── privkey.pem /.gitignore: -------------------------------------------------------------------------------- 1 | # environment file 2 | .env 3 | 4 | # wordpress files 5 | wordpress 6 | 7 | # mysql files 8 | dbdata 9 | 10 | # logging output 11 | logs 12 | 13 | # macOS and IDE 14 | .DS_Store 15 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Michael J. Stealey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WordPress (FPM Edition) - Docker 2 | 3 | Notes on deploying a single site [WordPress FPM Edition](https://hub.docker.com/_/wordpress/) instance as a docker deployment orchestrated by Docker Compose. 4 | 5 | - Use the FPM version of WordPress (v5-fpm) 6 | - Use MySQL as the database (v8) 7 | - Use Nginx as the web server (v1) 8 | - Use Adminer as the database management tool (v4) 9 | - Include self-signed SSL certificate ([Let's Encrypt localhost](https://letsencrypt.org/docs/certificates-for-localhost/) format) 10 | 11 | **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.** 12 | 13 | ## Table of contents 14 | 15 | - [Overview](#overview) 16 | - [Host requirements](#reqts) 17 | - [Configuration](#config) 18 | - [Deploy](#deploy) 19 | - [Adminer](#adminer) 20 | - [Teardown](#teardown) 21 | - [References](#references) 22 | - [Notes](#notes) 23 | 24 | ## Overview 25 | 26 | WordPress 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. 27 | 28 | This variant contains PHP-FPM, which is a FastCGI implementation for PHP. 29 | 30 | - See the [PHP-FPM website](https://php-fpm.org/) for more information about PHP-FPM. 31 | - 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. 32 | 33 | ### Host requirements 34 | 35 | Both Docker and Docker Compose are required on the host to run this code 36 | 37 | - Install Docker Engine: [https://docs.docker.com/engine/install/](https://docs.docker.com/engine/install/) 38 | - Install Docker Compose: [https://docs.docker.com/compose/install/](https://docs.docker.com/compose/install/) 39 | 40 | ## Configuration 41 | 42 | Copy the `env.template` file as `.env` and populate according to your environment 43 | 44 | ```ini 45 | # docker-compose environment file 46 | # 47 | # When you set the same environment variable in multiple files, 48 | # here’s the priority used by Compose to choose which value to use: 49 | # 50 | # 1. Compose file 51 | # 2. Shell environment variables 52 | # 3. Environment file 53 | # 4. Dockerfile 54 | # 5. Variable is not defined 55 | 56 | # Wordpress Settings 57 | export WORDPRESS_LOCAL_HOME=./wordpress 58 | export WORDPRESS_UPLOADS_CONFIG=./config/uploads.ini 59 | export WORDPRESS_DB_HOST=database:3306 60 | export WORDPRESS_DB_NAME=wordpress 61 | export WORDPRESS_DB_USER=wordpress 62 | export WORDPRESS_DB_PASSWORD=password123! 63 | 64 | # MySQL Settings 65 | export MYSQL_LOCAL_HOME=./dbdata 66 | export MYSQL_DATABASE=${WORDPRESS_DB_NAME} 67 | export MYSQL_USER=${WORDPRESS_DB_USER} 68 | export MYSQL_PASSWORD=${WORDPRESS_DB_PASSWORD} 69 | export MYSQL_ROOT_PASSWORD=rootpassword123! 70 | 71 | # Nginx Settings 72 | export NGINX_CONF=./nginx/default.conf 73 | export NGINX_SSL_CERTS=./ssl 74 | export NGINX_LOGS=./logs/nginx 75 | 76 | # User Settings 77 | # TBD 78 | ``` 79 | 80 | Modify `nginx/default.conf` and replace `$host` and `8443` with your **Domain Name** and exposed **HTTPS Port** throughout the file 81 | 82 | ```conf 83 | # default.conf 84 | # redirect to HTTPS 85 | server { 86 | listen 80; 87 | listen [::]:80; 88 | server_name $host; 89 | location / { 90 | # update port as needed for host mapped https 91 | rewrite ^ https://$host:8443$request_uri? permanent; 92 | } 93 | } 94 | 95 | server { 96 | listen 443 ssl http2; 97 | listen [::]:443 ssl http2; 98 | server_name $host; 99 | index index.php index.html index.htm; 100 | root /var/www/html; 101 | server_tokens off; 102 | client_max_body_size 75M; 103 | 104 | # update ssl files as required by your deployment 105 | ssl_certificate /etc/ssl/fullchain.pem; 106 | ssl_certificate_key /etc/ssl/privkey.pem; 107 | 108 | # logging 109 | access_log /var/log/nginx/wordpress.access.log; 110 | error_log /var/log/nginx/wordpress.error.log; 111 | 112 | # some security headers ( optional ) 113 | add_header X-Frame-Options "SAMEORIGIN" always; 114 | add_header X-XSS-Protection "1; mode=block" always; 115 | add_header X-Content-Type-Options "nosniff" always; 116 | add_header Referrer-Policy "no-referrer-when-downgrade" always; 117 | add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always; 118 | 119 | location / { 120 | try_files $uri $uri/ /index.php$is_args$args; 121 | } 122 | 123 | location ~ \.php$ { 124 | try_files $uri = 404; 125 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 126 | fastcgi_pass wordpress:9000; 127 | fastcgi_index index.php; 128 | include fastcgi_params; 129 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 130 | fastcgi_param PATH_INFO $fastcgi_path_info; 131 | } 132 | 133 | location ~ /\.ht { 134 | deny all; 135 | } 136 | 137 | location = /favicon.ico { 138 | log_not_found off; access_log off; 139 | } 140 | 141 | location = /favicon.svg { 142 | log_not_found off; access_log off; 143 | } 144 | 145 | location = /robots.txt { 146 | log_not_found off; access_log off; allow all; 147 | } 148 | 149 | location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ { 150 | expires max; 151 | log_not_found off; 152 | } 153 | } 154 | ``` 155 | 156 | Modify the `config/uploads.ini` file if the preset values are not to your liking (defaults shown below) 157 | 158 | ```ini 159 | file_uploads = On 160 | memory_limit = 256M 161 | upload_max_filesize = 75M 162 | post_max_size = 75M 163 | max_execution_time = 600 164 | ``` 165 | 166 | Included `uploads.ini` file allows for **Maximum upload file size: 75 MB** 167 | 168 | ![](./imgs/WP-media-filesize.png) 169 | 170 | ## Deploy 171 | 172 | Once configured the containers can be brought up using Docker Compose 173 | 174 | 1. Set the environment variables and pull the images 175 | 176 | ```console 177 | source .env 178 | docker-compose pull 179 | ``` 180 | 181 | 2. Bring up the Database and allow it a moment to create the WordPress user and database tables 182 | 183 | ```console 184 | docker-compose up -d database 185 | ``` 186 | 187 | You will know it's ready when you see something like this in the docker logs 188 | 189 | ```console 190 | $ docker-compose logs database 191 | wp-database | 2022-01-28 13:40:18+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.28-1debian10 started. 192 | wp-database | 2022-01-28 13:40:18+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql' 193 | wp-database | 2022-01-28 13:40:18+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.28-1debian10 started. 194 | wp-database | 2022-01-28 13:40:18+00:00 [Note] [Entrypoint]: Initializing database files 195 | ... 196 | wp-database | 2022-01-28 13:40:28+00:00 [Note] [Entrypoint]: Creating database wordpress 197 | wp-database | 2022-01-28 13:40:28+00:00 [Note] [Entrypoint]: Creating user wordpress 198 | wp-database | 2022-01-28 13:40:28+00:00 [Note] [Entrypoint]: Giving user wordpress access to schema wordpress 199 | wp-database | 200 | wp-database | 2022-01-28 13:40:28+00:00 [Note] [Entrypoint]: Stopping temporary server 201 | 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). 202 | 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. 203 | wp-database | 2022-01-28 13:40:31+00:00 [Note] [Entrypoint]: Temporary server stopped 204 | wp-database | 205 | wp-database | 2022-01-28 13:40:31+00:00 [Note] [Entrypoint]: MySQL init process done. Ready for start up. 206 | wp-database | 207 | ... 208 | 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 209 | 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. 210 | ``` 211 | 212 | 3. Bring up the WordPress and Nginx containers 213 | 214 | ```console 215 | docker-compose up -d wordpress nginx 216 | ``` 217 | 218 | After a few moments the containers should be observed as running 219 | 220 | ```console 221 | $ docker-compose ps 222 | NAME COMMAND SERVICE STATUS PORTS 223 | wp-database "docker-entrypoint.s…" database running 33060/tcp 224 | wp-nginx "/docker-entrypoint.…" nginx running 0.0.0.0:8080->80/tcp, 0.0.0.0:8443->443/tcp 225 | wp-wordpress "docker-entrypoint.s…" wordpress running 9000/tcp 226 | ``` 227 | 228 | The WordPress application can be reached at the designated host and port (e.g. [https://127.0.0.1:8443]()). 229 | 230 | - **NOTE**: you will likely have to acknowledge the security risk if using the included self-signed certificate. 231 | 232 | ![](./imgs/WP-first-run.png) 233 | 234 | Complete the initial WordPress installation process, and when completed you should see something similar to this. 235 | 236 | ![](./imgs/WP-dashboard.png) 237 | ![](./imgs/WP-view-site.png) 238 | 239 | ## Adminer 240 | 241 | An 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. 242 | 243 | Expose Adminer by uncommenting the `adminer` section of the `docker-compose.yml` file 244 | 245 | ```yaml 246 | ... 247 | # adminer - bring up only as needed - bypasses nginx 248 | adminer: 249 | # default port 8080 250 | image: adminer:4 251 | container_name: wp-adminer 252 | restart: unless-stopped 253 | networks: 254 | - wordpress 255 | depends_on: 256 | - database 257 | ports: 258 | - "9000:8080" 259 | ... 260 | ``` 261 | 262 | And run the `adminer` container 263 | 264 | ```console 265 | $ docker-compose up -d adminer 266 | [+] Running 2/2 267 | ⠿ Container wp-database Running 0.0s 268 | ⠿ Container wp-adminer Started 0.9s 269 | ``` 270 | 271 | Since 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/]()) 272 | 273 | ![](./imgs/WP-adminer.png) 274 | 275 | Enter the connection information for your Database and you should see something similar to image below. 276 | 277 | Example connection information: 278 | 279 | - System: **MySQL** 280 | - Server: **database** 281 | - Username: **wordpress** 282 | - Password: **password123!** 283 | - Database: **wordpress** 284 | 285 | **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 286 | 287 | ![](./imgs/WP-adminer-connected.png) 288 | 289 | When finished, stop and remove the `adminer` container. 290 | 291 | ```console 292 | $ docker-compose stop adminer 293 | [+] Running 1/1 294 | ⠿ Container wp-adminer Stopped 0.1s 295 | $ docker-compose rm -fv adminer 296 | Going to remove wp-adminer 297 | [+] Running 1/0 298 | ⠿ Container wp-adminer Removed 0.0s 299 | ``` 300 | 301 | ## Teardown 302 | 303 | For a complete teardown all containers must be stopped and removed along with the volumes and network that were created for the application containers 304 | 305 | Commands 306 | 307 | ```console 308 | docker-compose stop 309 | docker-compose rm -fv 310 | docker-network rm wp-wordpress 311 | # removal calls may require sudo rights depending on file permissions 312 | rm -rf ./wordpress 313 | rm -rf ./dbdata 314 | rm -rf ./logs 315 | ``` 316 | 317 | Expected output 318 | 319 | ```console 320 | $ docker-compose stop 321 | [+] Running 3/3 322 | ⠿ Container wp-nginx Stopped 0.3s 323 | ⠿ Container wp-wordpress Stopped 0.2s 324 | ⠿ Container wp-database Stopped 0.8s 325 | $ docker-compose rm -fv 326 | Going to remove wp-nginx, wp-wordpress, wp-database 327 | [+] Running 3/0 328 | ⠿ Container wp-nginx Removed 0.0s 329 | ⠿ Container wp-database Removed 0.0s 330 | ⠿ Container wp-wordpress Removed 0.0s 331 | $ docker network rm wp-wordpress 332 | wp-wordpress 333 | $ rm -rf ./wordpress 334 | $ rm -rf ./dbdata 335 | $ rm -rf ./logs 336 | ``` 337 | 338 | ## References 339 | 340 | - WordPress Docker images: [https://hub.docker.com/_/wordpress/](https://hub.docker.com/_/wordpress/) 341 | - MySQL Docker images: [https://hub.docker.com/_/mysql](https://hub.docker.com/_/mysql) 342 | - Nginx Docker images: [https://hub.docker.com/_/nginx/](https://hub.docker.com/_/nginx/) 343 | - Adminer Docker images: [https://hub.docker.com/_/adminer](https://hub.docker.com/_/adminer) 344 | 345 | --- 346 | 347 | ## Notes 348 | 349 | General information regarding standard Docker deployment of WordPress for reference purposes 350 | 351 | ### Let's Encrypt SSL Certificate 352 | 353 | Use: [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). 354 | 355 | ### Error establishing database connection 356 | 357 | This can happen when the `wordpress` container attempts to reach the `database` container prior to it being ready for a connection. 358 | 359 | ![](./imgs/WP-database-connection.png) 360 | 361 | This 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. 362 | 363 | ### Port Mapping 364 | 365 | Neither 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. 366 | 367 | If you wish to expose the ports to the host, you'd need to alter the stanzas for each in the `docker-compose.yml` file. 368 | 369 | For the `database` stanza, add 370 | 371 | ``` 372 | ports: 373 | - "3306:3306" 374 | ``` 375 | 376 | For the `wordpress` stanza, add 377 | 378 | ``` 379 | ports: 380 | - "9000:9000" 381 | ``` 382 | -------------------------------------------------------------------------------- /config/uploads.ini: -------------------------------------------------------------------------------- 1 | file_uploads = On 2 | memory_limit = 256M 3 | upload_max_filesize = 75M 4 | post_max_size = 75M 5 | max_execution_time = 600 -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | services: 3 | 4 | wordpress: 5 | # default port 9000 (FastCGI) 6 | image: wordpress:5-fpm 7 | container_name: wp-wordpress 8 | env_file: 9 | - .env 10 | restart: unless-stopped 11 | networks: 12 | - wordpress 13 | depends_on: 14 | - database 15 | volumes: 16 | - ${WORDPRESS_LOCAL_HOME}:/var/www/html 17 | - ${WORDPRESS_UPLOADS_CONFIG}:/usr/local/etc/php/conf.d/uploads.ini 18 | # - /path/to/repo/myTheme/:/var/www/html/wp-content/themes/myTheme 19 | environment: 20 | - WORDPRESS_DB_HOST=${WORDPRESS_DB_HOST} 21 | - WORDPRESS_DB_NAME=${WORDPRESS_DB_NAME} 22 | - WORDPRESS_DB_USER=${WORDPRESS_DB_USER} 23 | - WORDPRESS_DB_PASSWORD=${WORDPRESS_DB_PASSWORD} 24 | 25 | database: 26 | # default port 3306 27 | image: mysql:8 28 | container_name: wp-database 29 | env_file: 30 | - .env 31 | restart: unless-stopped 32 | networks: 33 | - wordpress 34 | environment: 35 | - MYSQL_DATABASE=${MYSQL_DATABASE} 36 | - MYSQL_USER=${MYSQL_USER} 37 | - MYSQL_PASSWORD=${MYSQL_PASSWORD} 38 | - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} 39 | volumes: 40 | - ${MYSQL_LOCAL_HOME}:/var/lib/mysql 41 | command: 42 | - '--default-authentication-plugin=mysql_native_password' 43 | 44 | nginx: 45 | # default ports 80, 443 - expose mapping as needed to host 46 | image: nginx:1 47 | container_name: wp-nginx 48 | env_file: 49 | - .env 50 | restart: unless-stopped 51 | networks: 52 | - wordpress 53 | depends_on: 54 | - wordpress 55 | ports: 56 | - "8080:80" # http 57 | - "8443:443" # https 58 | volumes: 59 | - ${WORDPRESS_LOCAL_HOME}:/var/www/html 60 | - ${NGINX_CONF}:/etc/nginx/conf.d/default.conf 61 | - ${NGINX_SSL_CERTS}:/etc/ssl:ro 62 | - ${NGINX_LOGS}:/var/log/nginx 63 | 64 | # adminer - bring up only as needed - bypasses nginx 65 | # adminer: 66 | # # default port 8080 67 | # image: adminer:4 68 | # container_name: wp-adminer 69 | # restart: unless-stopped 70 | # networks: 71 | # - wordpress 72 | # depends_on: 73 | # - database 74 | # ports: 75 | # - "9000:8080" 76 | 77 | networks: 78 | wordpress: 79 | name: wp-wordpress 80 | driver: bridge -------------------------------------------------------------------------------- /docker-compose.yml.defaults: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | services: 3 | 4 | wordpress: 5 | # default port 80 6 | image: wordpress:5-fpm 7 | container_name: wp-wordpress 8 | env_file: 9 | - .env 10 | restart: unless-stopped 11 | networks: 12 | - wordpress 13 | depends_on: 14 | - database 15 | volumes: 16 | - ${WORDPRESS_LOCAL_HOME:-./wordpress}:/var/www/html 17 | - ${WORDPRESS_UPLOADS_CONFIG:-./config/uploads.ini}:/usr/local/etc/php/conf.d/uploads.ini 18 | # - /path/to/repo/myTheme/:/var/www/html/wp-content/themes/myTheme 19 | environment: 20 | - WORDPRESS_DB_HOST=${WORDPRESS_DB_HOST:-database:3306} 21 | - WORDPRESS_DB_NAME=${WORDPRESS_DB_NAME:-wordpress} 22 | - WORDPRESS_DB_USER=${WORDPRESS_DB_USER:-wordpress} 23 | - WORDPRESS_DB_PASSWORD=${WORDPRESS_DB_PASSWORD:-password123!} 24 | 25 | database: 26 | # default port 3306 27 | image: mysql:8 28 | container_name: wp-database 29 | env_file: 30 | - .env 31 | restart: unless-stopped 32 | networks: 33 | - wordpress 34 | environment: 35 | - MYSQL_DATABASE=${MYSQL_DATABASE:-wordpress} 36 | - MYSQL_USER=${MYSQL_USER:-wordpress} 37 | - MYSQL_PASSWORD=${MYSQL_PASSWORD:-password123!} 38 | - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-rootpassword123!} 39 | volumes: 40 | - ${MYSQL_LOCAL_HOME:-./dbdata}:/var/lib/mysql 41 | command: 42 | - '--default-authentication-plugin=mysql_native_password' 43 | 44 | nginx: 45 | # default ports 80, 443 - expose mapping as needed to host 46 | image: nginx:1 47 | container_name: wp-nginx 48 | env_file: 49 | - .env 50 | restart: unless-stopped 51 | networks: 52 | - wordpress 53 | depends_on: 54 | - wordpress 55 | ports: 56 | - "8080:80" # http 57 | - "8443:443" # https 58 | volumes: 59 | - ${WORDPRESS_LOCAL_HOME:-./wordpress}:/var/www/html 60 | - ${NGINX_CONF:-./nginx/default.conf}:/etc/nginx/conf.d/default.conf 61 | - ${NGINX_SSL_CERTS:-./ssl}:/etc/ssl:ro 62 | - ${NGINX_LOGS:-./logs/nginx}:/var/log/nginx 63 | 64 | # adminer - bring up only as needed - bypasses nginx 65 | # adminer: 66 | # # default port 9000 67 | # image: adminer:4-fastcgi 68 | # container_name: wp-adminer 69 | # restart: unless-stopped 70 | # networks: 71 | # - wordpress 72 | # depends_on: 73 | # - database 74 | # ports: 75 | # - "9000:9000" 76 | 77 | networks: 78 | wordpress: 79 | name: wp-wordpress 80 | driver: bridge -------------------------------------------------------------------------------- /env.template: -------------------------------------------------------------------------------- 1 | # docker-compose environment file 2 | # 3 | # When you set the same environment variable in multiple files, 4 | # here’s the priority used by Compose to choose which value to use: 5 | # 6 | # 1. Compose file 7 | # 2. Shell environment variables 8 | # 3. Environment file 9 | # 4. Dockerfile 10 | # 5. Variable is not defined 11 | 12 | # Wordpress Settings 13 | export WORDPRESS_LOCAL_HOME=./wordpress 14 | export WORDPRESS_UPLOADS_CONFIG=./config/uploads.ini 15 | export WORDPRESS_DB_HOST=database:3306 16 | export WORDPRESS_DB_NAME=wordpress 17 | export WORDPRESS_DB_USER=wordpress 18 | export WORDPRESS_DB_PASSWORD=password123! 19 | 20 | # MySQL Settings 21 | export MYSQL_LOCAL_HOME=./dbdata 22 | export MYSQL_DATABASE=${WORDPRESS_DB_NAME} 23 | export MYSQL_USER=${WORDPRESS_DB_USER} 24 | export MYSQL_PASSWORD=${WORDPRESS_DB_PASSWORD} 25 | export MYSQL_ROOT_PASSWORD=rootpassword123! 26 | 27 | # Nginx Settings 28 | export NGINX_CONF=./nginx/default.conf 29 | export NGINX_SSL_CERTS=./ssl 30 | export NGINX_LOGS=./logs/nginx 31 | 32 | # User Settings 33 | # TBD -------------------------------------------------------------------------------- /imgs/WP-adminer-connected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjstealey/wordpress-nginx-docker/1f648915a674bfd0197e754b59412889401c194c/imgs/WP-adminer-connected.png -------------------------------------------------------------------------------- /imgs/WP-adminer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjstealey/wordpress-nginx-docker/1f648915a674bfd0197e754b59412889401c194c/imgs/WP-adminer.png -------------------------------------------------------------------------------- /imgs/WP-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjstealey/wordpress-nginx-docker/1f648915a674bfd0197e754b59412889401c194c/imgs/WP-dashboard.png -------------------------------------------------------------------------------- /imgs/WP-database-connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjstealey/wordpress-nginx-docker/1f648915a674bfd0197e754b59412889401c194c/imgs/WP-database-connection.png -------------------------------------------------------------------------------- /imgs/WP-first-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjstealey/wordpress-nginx-docker/1f648915a674bfd0197e754b59412889401c194c/imgs/WP-first-run.png -------------------------------------------------------------------------------- /imgs/WP-media-filesize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjstealey/wordpress-nginx-docker/1f648915a674bfd0197e754b59412889401c194c/imgs/WP-media-filesize.png -------------------------------------------------------------------------------- /imgs/WP-view-site.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjstealey/wordpress-nginx-docker/1f648915a674bfd0197e754b59412889401c194c/imgs/WP-view-site.png -------------------------------------------------------------------------------- /nginx/default.conf: -------------------------------------------------------------------------------- 1 | # default.conf 2 | # redirect to HTTPS 3 | server { 4 | listen 80; 5 | listen [::]:80; 6 | server_name $host; 7 | location / { 8 | # update port as needed for host mapped https 9 | rewrite ^ https://$host:8443$request_uri? permanent; 10 | } 11 | } 12 | 13 | server { 14 | listen 443 ssl http2; 15 | listen [::]:443 ssl http2; 16 | server_name $host; 17 | index index.php index.html index.htm; 18 | root /var/www/html; 19 | server_tokens off; 20 | client_max_body_size 75M; 21 | 22 | # update ssl files as required by your deployment 23 | ssl_certificate /etc/ssl/fullchain.pem; 24 | ssl_certificate_key /etc/ssl/privkey.pem; 25 | 26 | # logging 27 | access_log /var/log/nginx/wordpress.access.log; 28 | error_log /var/log/nginx/wordpress.error.log; 29 | 30 | # some security headers ( optional ) 31 | add_header X-Frame-Options "SAMEORIGIN" always; 32 | add_header X-XSS-Protection "1; mode=block" always; 33 | add_header X-Content-Type-Options "nosniff" always; 34 | add_header Referrer-Policy "no-referrer-when-downgrade" always; 35 | add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always; 36 | 37 | location / { 38 | try_files $uri $uri/ /index.php$is_args$args; 39 | } 40 | 41 | location ~ \.php$ { 42 | try_files $uri = 404; 43 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 44 | fastcgi_pass wordpress:9000; 45 | fastcgi_index index.php; 46 | include fastcgi_params; 47 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 48 | fastcgi_param PATH_INFO $fastcgi_path_info; 49 | } 50 | 51 | location ~ /\.ht { 52 | deny all; 53 | } 54 | 55 | location = /favicon.ico { 56 | log_not_found off; access_log off; 57 | } 58 | 59 | location = /favicon.svg { 60 | log_not_found off; access_log off; 61 | } 62 | 63 | location = /robots.txt { 64 | log_not_found off; access_log off; allow all; 65 | } 66 | 67 | location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ { 68 | expires max; 69 | log_not_found off; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ssl/README.md: -------------------------------------------------------------------------------- 1 | # SSL - certificates for development 2 | 3 | SSL development certificates are included in this repository for demonstration purposes and should be replaced with genuine certificates in production. 4 | 5 | - `privkey.pem` : the private key for your certificate. 6 | - `fullchain.pem`: the certificate file used in most server software (copy of chain.pem for development purposes). 7 | - `chain.pem` : used for OCSP stapling in Nginx >=1.3.7. 8 | 9 | These certificates are self signed, and as such not reckognized by any CA. Do not use this for anything beyond local development (Never use in production) 10 | 11 | ### Generate `privkey.pem`, `fullchain.pem`, `chain.pem` 12 | 13 | Certificate generation based on Let's Encrypt [certificates for localhost](https://letsencrypt.org/docs/certificates-for-localhost/). 14 | 15 | ``` 16 | openssl req -x509 -outform pem -out chain.pem -keyout privkey.pem \ 17 | -newkey rsa:4096 -nodes -sha256 -days 3650 \ 18 | -subj '/CN=localhost' -extensions EXT -config <( \ 19 | printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth") 20 | cat chain.pem > fullchain.pem 21 | ``` 22 | 23 | ### Reference 24 | 25 | Nginx configuration reference: [https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/) 26 | -------------------------------------------------------------------------------- /ssl/chain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE5TCCAs2gAwIBAgIJAKDNGmHgaI/NMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV 3 | BAMMCWxvY2FsaG9zdDAeFw0yMjAxMjExNDAwNThaFw0zMjAxMTkxNDAwNThaMBQx 4 | EjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC 5 | ggIBAMF7unNwZltgWhdtCoPJXYJ164UaeEme0lWAR+pT27HD2vkl6vQXV4Rqwpef 6 | YlpXlpygxZ3ShL8767cDmcMk/AVqmbaPMEUWrUE5ufPatJU/90m4HDgwoy448Q9l 7 | ZsqEe3L5Z2kkmEIByGVY/cnmMmBHPnRggEDer1QeKt5mefQA5B/bikeYPGuYSiWC 8 | B+QRJ8QY5YFQUZc2ygZh64ozXgcuHcaphsxfXbP7WvJNx+cx1lgBOMNDoK4376Bl 9 | yosFdnr7UOvI+0pCF73hpC7oc3M3RSi/jwoD0twnULwyP5YwA1MmwBtuIVA0BDqC 10 | AFPdE//L3fQERxbuTSShIXICDXPpDQKwxyeY/5e+1bmrVudBh98+Ddtcy6nbDnER 11 | LVFMSEZrYSOh4eebNRdgZC8A75+F87IhX++Nh9ora2BzT98zggYJNqSnfA0nUCXo 12 | Mx1+Z+heDYlUE/92nT/gRW1DjUMPn6Ge+p5DzCgvVWIvfj+x1qOOY1cV+I8REvJK 13 | roK9+L75fCLPdni02dyetBxa6O6hoQnvt+WnyPLOaef0TMxskQtiGQoBi19Tr/hL 14 | XjkIF8lVAewHG4lntosTnLSLy5SA+de9ZFwl7gi7IT7/fBhiEoFn5vCxB5INqK6j 15 | Vs2KayJOdJQRDxB1ty4SyGRohLwBlLV65KEOL5Mu1eorSZTzAgMBAAGjOjA4MBQG 16 | A1UdEQQNMAuCCWxvY2FsaG9zdDALBgNVHQ8EBAMCB4AwEwYDVR0lBAwwCgYIKwYB 17 | BQUHAwEwDQYJKoZIhvcNAQELBQADggIBAItWoIaimCtqJIH+KbkX6XIth3YBKSQS 18 | mnKszI0C0ScniWIdvFsKYOt9lmSmILN6M4YfEV9lEBBd64RbxvDkLVMkkpYSCuJl 19 | QlohBbGPjYHumWnyOhrB1zLYBs73ljbqetLSZjeGzbkz6xwUVHnkEnhvPCAzVnfY 20 | W2B4+/t2ng9qCO6i8JiAV64I849ZrQeCjp9giF0WEx9LSzzcc/pMly1frVP7Vx70 21 | xygXcyGfrd/t37mLVD3sXLDQxXFZPtII7WMFnj7F5IAGUQ7f/sgunjKTgkpJt53K 22 | T8rsBnZhEyxgaCKbMp659ft3uMXE58NKrCg8rZCa7Ld0TDiD6vZg4+RpzAl37sWr 23 | WRIvcK6zcSSIHtPnIvHHeZj2YgwRIOT8e649OoRVwo+4LgXA5GhkbUMSX/fkf8NQ 24 | 45S5mzlep0c/bStx6pLDz29RbqIlzK5Jo4Ugi2DkNB7snE8tEg5N+09vpwo/8tsV 25 | YP7CA7BlYaU6exAQiRx8gA97eGFEX4n8w8yRfuo5wcSAWDvKHWbwNcHUBNMMP3Fe 26 | oHnEEcFTNHDPz+P4swywdrOFcOF0tyhjQl/BEjf3/FrwHpiEX8YyXKJX26kg0M8q 27 | wU01nGDEBVnCTKkN6yjIH4+kGme0MQVrNItWWT/nOMS4iGtJYfvXBNVXd8uZujKw 28 | U+5MFPXQUvqR 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /ssl/fullchain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE5TCCAs2gAwIBAgIJAKDNGmHgaI/NMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV 3 | BAMMCWxvY2FsaG9zdDAeFw0yMjAxMjExNDAwNThaFw0zMjAxMTkxNDAwNThaMBQx 4 | EjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC 5 | ggIBAMF7unNwZltgWhdtCoPJXYJ164UaeEme0lWAR+pT27HD2vkl6vQXV4Rqwpef 6 | YlpXlpygxZ3ShL8767cDmcMk/AVqmbaPMEUWrUE5ufPatJU/90m4HDgwoy448Q9l 7 | ZsqEe3L5Z2kkmEIByGVY/cnmMmBHPnRggEDer1QeKt5mefQA5B/bikeYPGuYSiWC 8 | B+QRJ8QY5YFQUZc2ygZh64ozXgcuHcaphsxfXbP7WvJNx+cx1lgBOMNDoK4376Bl 9 | yosFdnr7UOvI+0pCF73hpC7oc3M3RSi/jwoD0twnULwyP5YwA1MmwBtuIVA0BDqC 10 | AFPdE//L3fQERxbuTSShIXICDXPpDQKwxyeY/5e+1bmrVudBh98+Ddtcy6nbDnER 11 | LVFMSEZrYSOh4eebNRdgZC8A75+F87IhX++Nh9ora2BzT98zggYJNqSnfA0nUCXo 12 | Mx1+Z+heDYlUE/92nT/gRW1DjUMPn6Ge+p5DzCgvVWIvfj+x1qOOY1cV+I8REvJK 13 | roK9+L75fCLPdni02dyetBxa6O6hoQnvt+WnyPLOaef0TMxskQtiGQoBi19Tr/hL 14 | XjkIF8lVAewHG4lntosTnLSLy5SA+de9ZFwl7gi7IT7/fBhiEoFn5vCxB5INqK6j 15 | Vs2KayJOdJQRDxB1ty4SyGRohLwBlLV65KEOL5Mu1eorSZTzAgMBAAGjOjA4MBQG 16 | A1UdEQQNMAuCCWxvY2FsaG9zdDALBgNVHQ8EBAMCB4AwEwYDVR0lBAwwCgYIKwYB 17 | BQUHAwEwDQYJKoZIhvcNAQELBQADggIBAItWoIaimCtqJIH+KbkX6XIth3YBKSQS 18 | mnKszI0C0ScniWIdvFsKYOt9lmSmILN6M4YfEV9lEBBd64RbxvDkLVMkkpYSCuJl 19 | QlohBbGPjYHumWnyOhrB1zLYBs73ljbqetLSZjeGzbkz6xwUVHnkEnhvPCAzVnfY 20 | W2B4+/t2ng9qCO6i8JiAV64I849ZrQeCjp9giF0WEx9LSzzcc/pMly1frVP7Vx70 21 | xygXcyGfrd/t37mLVD3sXLDQxXFZPtII7WMFnj7F5IAGUQ7f/sgunjKTgkpJt53K 22 | T8rsBnZhEyxgaCKbMp659ft3uMXE58NKrCg8rZCa7Ld0TDiD6vZg4+RpzAl37sWr 23 | WRIvcK6zcSSIHtPnIvHHeZj2YgwRIOT8e649OoRVwo+4LgXA5GhkbUMSX/fkf8NQ 24 | 45S5mzlep0c/bStx6pLDz29RbqIlzK5Jo4Ugi2DkNB7snE8tEg5N+09vpwo/8tsV 25 | YP7CA7BlYaU6exAQiRx8gA97eGFEX4n8w8yRfuo5wcSAWDvKHWbwNcHUBNMMP3Fe 26 | oHnEEcFTNHDPz+P4swywdrOFcOF0tyhjQl/BEjf3/FrwHpiEX8YyXKJX26kg0M8q 27 | wU01nGDEBVnCTKkN6yjIH4+kGme0MQVrNItWWT/nOMS4iGtJYfvXBNVXd8uZujKw 28 | U+5MFPXQUvqR 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /ssl/privkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | 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 51 | igsPvMAZsuT8bjgaZjA2B2xi2C01t0f0 52 | -----END PRIVATE KEY----- 53 | --------------------------------------------------------------------------------