├── .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 | 
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 | 
233 |
234 | Complete the initial WordPress installation process, and when completed you should see something similar to this.
235 |
236 | 
237 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------