#How to dockerize an existing WordPress and deploy it on AWS ECS
###Place this Dockerfile
in the root of your app repo
FROM php:5.6-apache
RUN a2enmod rewrite
# install the PHP extensions we need
RUN apt-get update && apt-get install -y libpng12-dev libjpeg-dev && rm -rf /var/lib/apt/lists/* \
&& docker-php-ext-configure gd --with-png-dir=/usr --with-jpeg-dir=/usr \
&& docker-php-ext-install gd mysqli opcache mbstring
# set recommended PHP.ini settings
# see https://secure.php.net/manual/en/opcache.installation.php
RUN { \
echo 'opcache.memory_consumption=128'; \
echo 'opcache.interned_strings_buffer=8'; \
echo 'opcache.max_accelerated_files=4000'; \
echo 'opcache.revalidate_freq=60'; \
echo 'opcache.fast_shutdown=1'; \
echo 'opcache.enable_cli=1'; \
} > /usr/local/etc/php/conf.d/opcache-recommended.ini
# Place the app code in its place
COPY . /var/www/html
# Place the entrypoint script in its place
COPY docker-entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# grr, ENTRYPOINT resets CMD now
ENTRYPOINT ["/entrypoint.sh"]
CMD ["apache2-foreground"]
What it does:
it install apache, PHP 5.6, and the extensions/packages that are needed by the application. Then it copies everything inside the directory where it is placed right inside the container folder, /var/www/html/
. After that, it copies the script docker-entrypoint.sh
inside the root folder of the container, it makes it executable and set it as the entrypoint. The last istruction (i.e. CMD ["apache2-foreground"]
) tells the container to run apache in foreground.
###Place this bash script docker-entrypoint.sh
in the root of your app repo
#!/bin/bash
set -e
chown -R www-data:www-data /var/www/html
rm -rf /var/www/html/docker-entrypoint.sh /var/www/html/Dockerfile
exec "$@"
What it does: it set the permissions (or better the owner and the group) of the application root and removes the unneeded files from it.
###Place this .dockerignore
in the root of your app repo
docker-compose.yml
.dockerignore
.env
.git
.gitignore
What it does: it let you exclude from the application root all the files that are not striclty related with the application code.
###Adjust the .gitignore
inside the root of your application repo
.env
vendor/
.DS_Store
*.sql
!dump.sql
cache
wp-content/uploads
wp-content/updraft
What it does:
it ignores the files and directories needed to continue develop locally (i.e. .env
, vendor/
) and the content of the folders where the user contents get saved (i.e. wp-content/uploads
, wp-content/updraft
, etc.).
NB: these folders (i.e. wp-content/uploads
, wp-content/updraft
, etc.) still needs to be tracked but they need to empty. Their content should be ignored. To accomplish this, place a .gitkeep
file inside the aforementioned directories.
###Substitute your default wp-config.php
with this one
<?php
/**
* The base configurations of a Dockerized WordPress.
* You can find more information by visiting
* {@link http://codex.wordpress.org/Editing_wp-config.php Editing wp-config.php}
*/
# Load the .env if present.
if (file_exists(dirname(__FILE__).DIRECTORY_SEPARATOR.'.env')) {
require 'vendor/autoload.php';
$dotenv = new Dotenv\Dotenv(__DIR__);
$dotenv->load();
}
# The name of the database for WordPress.
define('DB_NAME', getenv('WORDPRESS_DB_NAME'));
# MySQL database username.
define('DB_USER', getenv('WORDPRESS_DB_USER'));
# MySQL database password.
define('DB_PASSWORD', getenv('WORDPRESS_DB_PASSWORD'));
# MySQL hostname.
define('DB_HOST', getenv('WORDPRESS_DB_HOST'));
# Database Charset to use in creating database tables.
define('DB_CHARSET', getenv('WORDPRESS_DB_CHARSET'));
# The Database Collate type. Don't change this if in doubt.
define('DB_COLLATE', getenv('WORDPRESS_DB_COLLATE'));
/**
* Authentication Unique Keys and Salts.
*
* Change these to different unique phrases!
* You can generate these using the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}
* You can change these at any point in time to invalidate all existing cookies. This will force all users to have to log in again.
*
* @since 2.6.0
*/
define('AUTH_KEY', getenv('WORDPRESS_AUTH_KEY'));
define('SECURE_AUTH_KEY', getenv('WORDPRESS_SECURE_AUTH_KEY'));
define('LOGGED_IN_KEY', getenv('WORDPRESS_LOGGED_IN_KEY'));
define('NONCE_KEY', getenv('WORDPRESS_NONCE_KEY'));
define('AUTH_SALT', getenv('WORDPRESS_AUTH_SALT'));
define('SECURE_AUTH_SALT', getenv('WORDPRESS_SECURE_AUTH_SALT'));
define('LOGGED_IN_SALT', getenv('WORDPRESS_LOGGED_IN_SALT'));
define('NONCE_SALT', getenv('WORDPRESS_NONCE_SALT'));
# Enable Amazon S3 and Cloudfront.
define('AWS_ACCESS_KEY_ID', getenv('WORDPRESS_AWS_ACCESS_KEY_ID'));
define('AWS_SECRET_ACCESS_KEY', getenv('WORDPRESS_AWS_SECRET_ACCESS_KEY'));
# Enable WP Cache.
define('WP_CACHE', getenv('WORDPRESS_WP_CACHE'));
define('WPCACHEHOME', getenv('WORDPRESS_WPCACHEHOME')); //Added by WP-Cache Manager
# Enable Secure Logins.
define('FORCE_SSL_LOGIN', getenv('WORDPRESS_FORCE_SSL_LOGIN'));
define('FORCE_SSL_ADMIN', getenv('WORDPRESS_FORCE_SSL_ADMIN'));
# Use external cron.
define('DISABLE_WP_CRON', getenv('WORDPRESS_DISABLE_WP_CRON'));
/**
* WordPress Database Table prefix.
*
* You can have multiple installations in one database if you give each a unique
* prefix. Only numbers, letters, and underscores please!
*/
$table_prefix = getenv('WORDPRESS_TABLE_PREFIX');
/**
* WordPress Localized Language, defaults to English.
*
* Change this to localize WordPress. A corresponding MO file for the chosen
* language must be installed to wp-content/languages. For example, install
* de_DE.mo to wp-content/languages and set WPLANG to 'de_DE' to enable German
* language support.
*/
define('WPLANG', getenv('WORDPRESS_WPLANG'));
/**
* For developers: WordPress debugging mode.
*
* Change this to true to enable the display of notices during development.
* It is strongly recommended that plugin and theme developers use WP_DEBUG
* in their development environments.
*/
define('WP_DEBUG', getenv('WORDPRESS_WP_DEBUG'));
# Absolute path to the WordPress directory.
if (!defined('ABSPATH'))
define('ABSPATH', dirname(__FILE__).DIRECTORY_SEPARATOR);
# To enable the possibility to update plugins directly from back-end (locally).
define('FS_METHOD', getenv('WORDPRESS_FS_METHOD'));
# Disable updates of plugins and themes together with plugins and themes editor.
define('DISALLOW_FILE_MODS', getenv('WORDPRESS_DISALLOW_FILE_MODS'));
# Disable all automatic updates.
define('AUTOMATIC_UPDATER_DISABLED', getenv('WORDPRESS_AUTOMATIC_UPDATER_DISABLED'));
# How to handle all core updates.
define('WP_AUTO_UPDATE_CORE', getenv('WORDPRESS_WP_AUTO_UPDATE_CORE'));
# WP Siteurl.
define('WP_SITEURL', getenv('WORDPRESS_WP_SITEURL'));
# WP Home.
define('WP_HOME', getenv('WORDPRESS_WP_HOME'));
# Enable Multisite.
define('WP_ALLOW_MULTISITE', getenv('WORDPRESS_WP_ALLOW_MULTISITE'));
# Uploads directory.
define('UPLOADS', getenv('WORDPRESS_UPLOADS'));
/** Sets up WordPress vars and included files. */
require_once(ABSPATH.'wp-settings.php');
What it does: it defines almost all the constants that can be used inside a wp-config.php
and valorize them with environment variables.
Given the behavior of the getenv()
function, the constants that are boolean by definition (e.g. WP_DEBUG
) should not be defined if they need to be false
. The getenv('SOME_ENV_VARIABLE')
returns indeed false
if 'SOME_ENV_VARIABLE'
is not defined.
###Adjust your local environment
Given the new wp-config.php
, to continue develop on your local environment you need to:
- install composer
- install phpdotenv
- place an
.env
file in the root of the app repo with all the environment variables that your app need to be running. Here there is an example of a possible.env
file:
WORDPRESS_DB_NAME=db_name
WORDPRESS_DB_USER=db_user
WORDPRESS_DB_PASSWORD=db_password
WORDPRESS_DB_HOST=localhost
WORDPRESS_DB_CHARSET=utf8
WORDPRESS_DB_COLLATE=
WORDPRESS_AUTH_KEY=
WORDPRESS_SECURE_AUTH_KEY=
WORDPRESS_LOGGED_IN_KEY=
WORDPRESS_NONCE_KEY=
WORDPRESS_AUTH_SALT=
WORDPRESS_SECURE_AUTH_SALT=
WORDPRESS_LOGGED_IN_SALT=
WORDPRESS_NONCE_SALT=
WORDPRESS_AWS_ACCESS_KEY_ID=MYACCESSKEY
WORDPRESS_AWS_SECRET_ACCESS_KEY=MYACCESSKEY
WORDPRESS_TABLE_PREFIX=wp_
WORDPRESS_WPLANG=en_US
WORDPRESS_WP_DEBUG=
WORDPRESS_FS_METHOD=direct
WORDPRESS_WP_SITEURL=http://my-site.test
WORDPRESS_WP_HOME=http://my-site.test
By following the aforementioned steps you should be able to continue develop you app as before. The modified version of the wp-config.php
take into consideration the possible presence of the .env
file and loads the environment variables making them available to you app:
# Load the .env if present.
if (file_exists(dirname(__FILE__).DIRECTORY_SEPARATOR.'.env')) {
require 'vendor/autoload.php';
$dotenv = new Dotenv\Dotenv(__DIR__);
$dotenv->load();
}
###Set up a remote image building service We use Quay.io at the time I'm writing this guide. In this way you get automatically builded images when you push on the app repo.
###Set up the AWS ECS Create a cluster for your application (name it as your app repo).
Create a Task
definition represeting your application with all the container that it needs to run. Tasks
could be configured through the UI or a JSON
file. You can set the required environment variables needed by the wp-config.php
right during the creation of the Task
. An example of the "environment"
section inside the JSON
configuration is the following one:
...
"environment": [
{
"name": "WORDPRESS_DB_USER",
"value": "name_of_the_db_user"
},
{
"name": "WORDPRESS_DB_CHARSET",
"value": "utf8"
},
{
"name": "WORDPRESS_DB_HOST",
"value": "the_host_where_the_db_is_placed" # possibly an RDS endpoint
},
{
"name": "WORDPRESS_NONCE_KEY",
"value": "long_random_key"
},
{
"name": "WORDPRESS_DB_NAME",
"value": "name_of_the_app_db"
},
{
"name": "WORDPRESS_AWS_ACCESS_KEY_ID",
"value": "aws_access_key"
},
{
"name": "WORDPRESS_LOGGED_IN_KEY",
"value": "long_random_key"
},
{
"name": "WORDPRESS_UPLOADS",
"value": "location_of_the_uploads_directory" # by default `wp-content/uploads`
},
{
"name": "WORDPRESS_DISABLE_WP_CRON",
"value": "true"
},
{
"name": "WORDPRESS_AWS_SECRET_ACCESS_KEY",
"value": "aws_secret_access_key"
},
{
"name": "WORDPRESS_DB_PASSWORD",
"value": "db_password_of_the_db_user"
},
{
"name": "WORDPRESS_SECURE_AUTH_KEY",
"value": "long_random_key"
},
{
"name": "WORDPRESS_TABLE_PREFIX",
"value": "table_prefix" # by default `wp_`
},
{
"name": "WORDPRESS_WPLANG",
"value": "en_GB"
},
...
]
...
Create a Service
to handle the delivery of your application and associate an ELB
with it.
The ELB
will need to be associated with an Auto Scaling Group
(i.e. ASG
) which in turn will need to be associated with a Launching Configuration
.The ELB
, the Auto Scaling Group
(i.e. ASG
) and the Launching Configuration
are the entities that control the number of EC2 instances on which your Service
runs on.
The number of EC2 instances that need to run by a Service
to handle the deployment of a new version of your app is always the desired number of EC2 plus one. One instance should always be "free", i.e. there should not be containers that "occupy" the ports needed by the deploying container.
The EC2 instances that will run your Service
need to be ECS optimized, i.e. they need to have installed the ECS demon. They also need to have a proper IAM role
that will let them be handled by the ASG
. The first time you create a cluster you get the opportunity to automatically create a proper IAM role
, i.e. ecsInstanceRole
.
The deploy of a new version of your app is done by creating a new Revision
of the Task
in charge of delivering the app itself.
###Deploy a new version of the app Use this script:
- download it (just the script),
- make it executable (
chmod +x ecs-deploy
), - install jq.
- You will also need the aws-cli.
The script has still some problems (e.g. with the region parameter) but if you go with the following command you shouldn't encounter any problem:
ecs-deploy -k AWS_KEY -s AWS_SECRET_KEY -c CLUSTER -n NAME_OF_SERVICE_TO_DEPLOY -i repo.of.image.building.service/ORGANIZATION/REPO_NAME:TAG
What it does: it wraps the aws-cli commands needed to update the service that handles the task that delivers your app. This is done by creating a new revision of the over aforementioned task