The Complete WordPress Guide From Scratch With Woocommerce And Visual Composer | Simpliv

The Complete WordPress Guide From Scratch With Woocommerce And Visual Composer | Simpliv

The Complete WordPress Guide From Scratch With Woocommerce And Visual Composer

WordPress is an open source blogging tool and content management system, thousands of programmers around the world working on WordPress to improve the WordPress functionality because of it an open source project. CMS(Content Management System) means if you are not familiar with coding stuff like PHP, jQuery or any programming langue then you still create your own professional website using WordPress.

Thousand of themes available to use you just need to download the theme and activate it without changes and coding that’s why WordPress rocks, you can also add much functionality by adding the WordPress or third-party plugins. We will discuss various thing in this course i.e most powerful plugins Woocomerc so you can easily create the E-commerce website in WordPress and also we discuss the visual composer plugin to build the page quickly with the simple click, its a page builder. We cover each and every topic from WordPress, if you are the non-technical person you can also be enrolled in this course because this course is designed for all Non-technical and technical persons.

If you are a No-technical person and you want to create and manage your own website professionally in Wordpress this is course design for you and beginners. I will cover all topics in this series related to WordPress like visual composer which is most popular and powerful plugin to create WordPress pages by drag and drop, I will also cover all the topics related to Woocommerce while woking E-commerce system in WordPress.

I will use pay pal payment gateway in Woocommerce so you can easily understand the concepts of the E-commerce system.

We cover many plugins which are required for every theme like SEO Yoast.

Who is the target audience?

Every one who want to leran wordpress from scratch
Non-technical person who want to learn Wordpress
Every one who want to leran E-commerce in wordpress
NOTE: We are adding more lectures into the course.

Basic knowledge
Computer with internet Connection
To continue:

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

What's new in Bootstrap 5 and when Bootstrap 5 release date?

What’s new in HTML6

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

Step by Step guide to Write your own WordPress Template

Step by Step guide to Write your own WordPress Template

Step by Step guide to Write your own WordPress Template - Writing your own WordPress template from scratch is fairly simple. If you are into Web Development industry, you might’ve already heard what “WordPress” is...

Step by Step guide to Write your own WordPress Template - Writing your own WordPress template from scratch is fairly simple. If you are into Web Development industry, you might’ve already heard what “WordPress” is...

Maybe a client has mentioned, but you’re not familiar with it. Maybe you’ve already worked with it before, but don’t know how to make a theme from scratch. Or maybe you’re a complete newbie. Whatever is the case, this post is for you.

**Prerequisites: **Before we begin, you’ll need to fulfill the following set of requirements.
You need to have a fully fledged WordPress setup, either on localhost or live hosting. If you want to learn more about starting with WordPress, refer to this article.A conceptual design, either as PSD or HTML CSS to follow throughout the development process.A little introduction to PHP programming. However, it’s not a necessity for this particular post but still recommended.## Scope

Designing a WordPress theme is a long, tedious, never ending but a great programming challenge. The development process depends entirely on how you want your theme to look like. This post is just a tutorial and does not cover all the bits and pieces required for a standard WordPress theme. After going through this article, you have to heavily rely on WordPress Codex and WordPress StackExchange for your further queries.

Getting Started

With the prerequisites in mind, let’s get started. The very first thing you need to know is the fact that almost everything you do in WordPress is inside the wp-content directory. Everything else is the WordPress CMS itself and you don’t want to mess with that.When you’ll open wp-content -> theme directory, you’ll find default WordPress themes, like twentyfifteen, twentyfourteen, twentythirteen, etc. To start with one of your own, create a directory with whatever name you prefer. For this post, we’ll call it wpstart.

*A WordPress theme atleast needs two files to exist – style.css and *index.php
So go into wpstart folder and create these two files. In style.css, insert the following comment. This tells the WordPress dashboard about the theme details and meta information.

/* 
Theme Name: WP Start 
Author: FedUser 
Description: A bare bone WordPress theme 
Version: 0.0.1 
*/

Now switch to your WordPress dashboard, and click on Appearance > Themes. You’ll find WP Start in your theme collection.

Go ahead and activate this theme, and then visit the site. And Voila! You’ve technically created a custom theme, all by yourself. Of course, it doesn’t do anything except it has a blank white screen. This is where index.php comes into action.

Open index.php in text editor and write in the following code.

<!DOCTYPE html> 
<html> 
<body> 
<h1>This is a sample WordPress theme.</h1> 
</body> 
</html> 

Visit the site once again and get your first WordPress template up and running.

Divide and Conquer

To develop a standard WordPress theme, you need to divide all your work into sections. This is not necessary, as you can do everything in index.php, but a good programming practice involves modularity. For this particular post, we will divide our entire work into four sections, viz. header, footer, sidebar and content. Corresponding to these sections, we will create four different files, namely header.php, footer.php, sidebar.php and content.php.header.php: For this particular example, this file will do the following;Define all the meta and link tags inside <head> for HTML.Display site branding like name and description.Provide navigation to different pages.With these points in mind, let’s code our theme header.

<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8" /> 
<meta name="viewport" content="width=device-width, initial-scale=1" /> 

<title>WP Start</title> 

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
		integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
		crossorigin="anonymous" /> 
</head> 

<body> 

<nav></nav> 

Now there is one thing I want to bring your attention to. You can see how “hard-coded” our site title is. Meaning, the title is going to remain the same “WP Start”, no matter what site you apply this theme on. If the author has to change it, he has to manually edit the code to do so. In order to avoid these manual tweaking of templates, WordPress provides various function calls to deal with these situations dynamically. In this particular case, I want the title to be the name of the site/blog. For this, I’ll replace

<title>WP Start<title>

with

<title> <?php echo get_bloginfo( "name" ); ?> </title>

This is called embedding small php excerpt into HTML. (Technically, we are writing HTML in php file. So we’re embedding HTML in php code).

So the header.php, with some additional code, becomes;

<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8" /> 
<meta name="viewport" content="width=device-width, initial-scale=1" /> 


<title> <?php echo get_bloginfo( "name" ); ?> </title> 


<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
		integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
		crossorigin="anonymous" /> 


<link rel="stylesheet" href="<?php echo get_bloginfo( 'template_directory' ); ?>/style.css" /> 
<?php wp_head(); ?> 
</head> 


<body> 


<nav class="navbar navbar-default"> 
	<div class="container"> 
	<div class="navbar-header"> 
		<a class="navbar-brand" href="<?php echo esc_url( home_url() ); ?>"> 
		<h3 class="site-branding"> <?php echo get_bloginfo( "name" ); ?> </h3> 
		</a> 
	</div> 


	<ul class="nav navbar-nav navbar-right"> 
		<li class="active"><a href="#">Home</a></li> 
		<li><a href="#">Contact</a></li> 
		<li><a href="#">About</a></li> 
	</ul> 
	</div> 
</nav> 


Additional php excerpts used in this code are;

<?php echo get_bloginfo( 'template_directory' ); ?>

This is to get the directory of the template, so that addition resources like CSS, JS, fonts, etc. can be located.

<?php echo esc_url( home_url() ); ?>

This will echo the homepage url of the site.

footer.php: This is the file where we will add whatever we want in the site footer, like custom footer, script tags, etc. Also, the HTML tags that started in header.php are closed in this file.

<footer class="site-footer"> 
	<div class="container"> 
	<div class="row row-30"> 
		<div class="col-md-4 col-xl-5"> 
		<div class="pr-xl-4"> 
			<h3> 
			<a href="<?php echo esc_url( home_url() ); ?>"> 
				<?php echo get_bloginfo( "name" ); ?> 
			</a> 
			</h3> 
			<p><?php echo get_bloginfo( "description" ); ?></p> 
			<p>© 2018 FedUser. All Rights Reserved.</p> 
		</div> 
		</div> 


		<div class="col-md-4"> 
		<h5>Contacts</h5> 
		<dl class="contact-list"> 
			<dt>Address:</dt> 
			<dd>798 ABC Nagar, JKL, Rajasthan</dd> 
		</dl> 
		<dl class="contact-list"> 
			<dt>e-Mail:</dt> 
			<dd><a href="mailto:#">[email protected]</a></dd> 
		</dl> 
		<dl class="contact-list"> 
			<dt>Phone No.:</dt> 
			<dd><a href="tel:#">+91 1234567890</a> 
			</dd> 
		</dl> 
		</div> 


		<div class="col-md-4 col-xl-3"> 
		<h5>Links</h5> 
		<ul class="nav-list"> 
			<li><a href="#">About</a></li> 
			<li><a href="#">Projects</a></li> 
			<li><a href="#">Blog</a></li> 
			<li><a href="#">Contacts</a></li> 
			<li><a href="#">Pricing</a></li> 
		</ul> 
		</div> 
	</div> 
	</div> 
</footer> 


<script src="https://code.jquery.com/jquery-1.12.4.min.js"
	integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ="
	crossorigin="anonymous"> 
</script> 


<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
	integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
	crossorigin="anonymous"> 
</script> 


</body> 
</html> 


The additional php excerpt used in this file is;

<?php echo get_bloginfo( "description" ); ?>

This will fetch and place the site description.Another thing to mention here is that I have used “hard-coded” sub-sections like “Contacts” and “Links” in the footer.php file. Instead, you can use WordPress Widgets to automate and make them modifiable directly via Customizer. This, however, is beyond the scope of this post and we’ll discuss it any time in the future articles.sidebar.php: Most of the websites have a sidebar, so do ours. Often sidebars display archive links, recent posts, social media accounts, advertisements, etc. In our case, we’ll go with archive links and social media links. Again, a WordPress widget is way better than the “hard-coded” junk! But for the sake of clarity, we’ll stick to the latter.

<div class="sidebar"> 
<div class="widget"> 
	<h3 class="widget-title">Archives</h3> 
	<div class="widget-content"> 
	<ul> 
		<li><a href="#">October 2018</a></li> 
		<li><a href="#">November 2018</a></li> 
		<li><a href="#">December 2018</a></li> 
	</ul> 
	</div> 
</div> 


<div class="widget"> 
	<h3 class="widget-title">Social</h3> 
	<div class="widget-content"> 
	<ul> 
		<li><a href="#">Facebook</a></li> 
		<li><a href="#">Twitter</a></li> 
		<li><a href="#">LinkedIn</a></li> 
		<li><a href="#">Github</a></li> 
	</ul> 
	</div> 
</div> 
</div> 


content.php: Now that header, footer and sidebar are all set up, we’ll move towards the main content of the site. For the moment, we will stick to some dummy content inside this file.

<div class="main-content"> 
<h3>Sample Title</h3> 
<p>Sample text goes here.......</p> 
</div> 


Integration

Now let’s move back to the index.php where we will integrate all the above sections into one. As this file is an entry point for our theme, we can cleverly choose to put these sections. Here’s how I’ve done it.

<?php get_header(); ?> 


<div class="container"> 
<div class="row"> 
	<div class="col-md-9"> 
	<?php get_template_part( 'content', get_post_format() ); ?> 
	</div> 
	<div class="col-md-3"> 
	<?php get_sidebar(); ?> 
	</div> 
</div> 
</div> 


<?php get_footer(); ?> 


The php excerpts used here are self explanatory. get<em>header(), get</em>sidebar() and get_footer() are predefined functions used for embedding corresponding sections. For a custom section like content.php, embedding is done by the following code;

<?php get_template_part( 'content', get_post_format() ); ?>

style.css: Now that we have updated our index.php file, let’s add some charm with CSS.

/* 
Theme Name: WP Start 
Author: FedUser 
Description: A bare bone WordPress theme 
Version: 0.0.1 
*/


nav.navbar .navbar-brand .site-branding { 
margin: 0; 
} 


footer.site-footer { 
background-color: #502550; 
color: #fff; 
padding: 40px 0 20px 0; 
} 


footer.site-footer a { 
color: #fff; 
} 


And Voila! The first look of your custom WordPress theme is ready.

The Loop

This is the most exciting part of the entire WordPress theme development where you have control of all the posts. The Loop is a functionality with which you can dynamically insert content into your theme. Our aim in this tutorial is to present all the blog posts as a user-friendly list so that the reader can choose any one of them. Let’s see how we do it.The loop itself is self-explanatory.

<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?> 


<!-- contents of the loop --> 


<?php endwhile; endif; ?> 


IF there are any posts, WHILE there are none left, DISPLAY them. Anything inside this loop will be repeated, till the page runs out of all the posts. We can use this concept to display our list. Here’s how I have done it.

<div class="panel panel-default blog-post"> 
<div class="panel-heading"> 
	<h3 class="panel-title post-title"> 


	<?php if( !is_single() ): ?> 


		<a href="<?php echo esc_url( get_permalink() ); ?>"> 
		<?php the_title(); ?> 
		</a> 


	<?php else: 
		the_title(); 
	endif; ?> 


	</h3> 


	<p class="post-meta"> 
	<?php the_date(); ?> 
	by <a href="#"> 
		<?php the_author(); ?> 
		</a> 
	</p> 
</div> 


<div class="panel-body"> 


	<?php if( !is_single() ): 
	the_excerpt(); 
	else: 
	the_content(); 
	endif; ?> 


</div> 
</div> 


And changed the index.php to this.

<?php get_header(); ?> 


<div class="container"> 
<div class="row"> 
	<div class="col-md-9 blog-main"> 
	<?php if( have_posts() ): 
			while( have_posts() ): 


				the_post(); 
				get_template_part( 'content', get_post_format() ); 


			endwhile; 
			endif; 
	?> 
	</div> 
	<div class="col-md-3"> 
	<?php get_sidebar(); ?> 
	</div> 
</div> 
</div> 


<?php get_footer(); ?> 


Let’s look at what just happened!

The Loop in index.php is calling the content.php everytime the page has a post. Inside content.php, I’ve checked if the current post is_single(). This condition will hold true if the current page contains only a single post to loop through. When it is not single, I wanted a link to that post via its title. So I used get_permalink() to get the url of that particular post. However, if the page is single, there is no need of a link and therefore, I’ve used only the_title() function.

Moving on to the meta info of the post. I’ve displayed the_date() on which the article was published and its the_author().

Finally, I’ve used the same concept of is_single() to either display the_excerpt() or the_content() of the post.

See, it was that easy and fun. Now with a little charm of CSS, I got the following result.

Conclusion:
We’re ending this post on this very point but you need to know that there is still a lot to learn about WordPress. It was just an example drill but a standard theme would be much complex. Still, we hope that you’ve learnt something new.If there was something that you didn’t understand, do mention it in the comments. If there is something that needs to be corrected, please let us know! If you’ve any feedback or suggestions for further improvements, we would highly appreciate that as well.We would love to see what you’ve learnt through this post. So do share links to your first WordPress themes. Your first steps can cheer up the new-comers.

Install WordPress With Docker Compose

Install WordPress With Docker Compose

Docker Compose manages to simplify the installation process to a single deployment command greatly reducing the time and effort required...

Introduction

WordPress is a free and open-source Content Management System (CMS) built on a MySQLdatabase with PHP processing. Thanks to its extensible plugin architecture and templating system, and the fact that most of its administration can be done through the web interface, WordPress is a popular choice when creating different types of websites, from blogs to product pages to eCommerce sites.

Running WordPress typically involves installing a LAMP (Linux, Apache, MySQL, and PHP) or LEMP (Linux, Nginx, MySQL, and PHP) stack, which can be time-consuming. However, by using tools like Docker and Docker Compose, you can simplify the process of setting up your preferred stack and installing WordPress. Instead of installing individual components by hand, you can use images, which standardize things like libraries, configuration files, and environment variables, and run these images in containers, isolated processes that run on a shared operating system. Additionally, by using Compose, you can coordinate multiple containers — for example, an application and database — to communicate with one another.

In this tutorial, you will build a multi-container WordPress installation. Your containers will include a MySQL database, an Nginx web server, and WordPress itself. You will also secure your installation by obtaining TLS/SSL certificates with Let's Encrypt for the domain you want associated with your site. Finally, you will set up a <a href="https://www.digitalocean.com/community/tutorials/how-to-schedule-routine-tasks-with-cron-and-anacron-on-a-vps" target="_blank">cron</a> job to renew your certificates so that your domain remains secure.

Prerequisites

To follow this tutorial, you will need:

  • A server running Ubuntu 18.04, along with a non-root user with sudo privileges and an active firewall. For guidance on how to set these up, please see this Initial Server Setup guide.
  • Docker installed on your server, following Steps 1 and 2 of How To Install and Use Docker on Ubuntu 18.04.
  • Docker Compose installed on your server, following Step 1 of How To Install Docker Compose on Ubuntu 18.04.
  • A registered domain name. This tutorial will use example.com throughout. You can get one for free at Freenom, or use the domain registrar of your choice.
  • Both of the following DNS records set up for your server. You can follow this introduction to DigitalOcean DNS for details on how to add them to a DigitalOcean account, if that’s what you’re using:
    An A record with example.com pointing to your server's public IP address.An A record with www.example.com pointing to your server's public IP address.## Step 1 — Defining the Web Server Configuration

Before running any containers, our first step will be to define the configuration for our Nginx web server. Our configuration file will include some WordPress-specific location blocks, along with a location block to direct Let's Encrypt verification requests to the Certbot client for automated certificate renewals.

First, create a project directory for your WordPress setup called wordpress and navigate to it:

mkdir wordpress && cd wordpress

Next, make a directory for the configuration file:

mkdir nginx-conf

Open the file with nano or your favorite editor:

nano nginx-conf/nginx.conf

In this file, we will add a server block with directives for our server name and document root, and location blocks to direct the Certbot client's request for certificates, PHP processing, and static asset requests.

Paste the following code into the file. Be sure to replace example.com with your own domain name:

~/wordpress/nginx-conf/nginx.conf

server 
      { listen 80; 
      listen [::]:80;

      server_name example.com www.example.com;
      
      index index.php index.html index.htm;
      
      root /var/www/html;

      location ~ /.well-known/acme-challenge {
              allow all;
              root /var/www/html;
      } 

      location / 
              { try_files $uri $uri/ /index.php$is_args$args; 
      }
      location ~ \.php$ { 
              try_files $uri =404; 
              fastcgi_split_path_info ^(.+\.php)(/.+)$; 
              fastcgi_pass wordpress:9000; 
              fastcgi_index index.php; 
              include fastcgi_params; 
              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 
              fastcgi_param PATH_INFO $fastcgi_path_info;
      } 

      location ~ /\.ht { 
              deny all; 
      }
      location = /favicon.ico { 
              log_not_found off; access_log off;
 
      } 
      location = /robots.txt { 
              log_not_found off; access_log off; allow all; 

      } 
      location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ { 
              expires max; 
              log_not_found off;
     } 
}

Our server block includes the following information:

Directives:

  • listen: This tells Nginx to listen on port 80, which will allow us to use Certbot's webroot plugin for our certificate requests. Note that we are not including port 443 yet — we will update our configuration to include SSL once we have successfully obtained our certificates.
  • server_name: This defines your server name and the server block that should be used for requests to your server. Be sure to replace example.com in this line with your own domain name.
  • index: The index directive defines the files that will be used as indexes when processing requests to your server. We've modified the default order of priority here, moving index.phpin front of index.html so that Nginx prioritizes files called index.php when possible.
  • root: Our root directive names the root directory for requests to our server. This directory, /var/www/html, is created as a mount point at build time by instructions in our WordPress Dockerfile. These Dockerfile instructions also ensure that the files from the WordPress release are mounted to this volume.

Location Blocks:

  • location ~ /.well-known/acme-challenge: This location block will handle requests to the .well-known directory, where Certbot will place a temporary file to validate that the DNS for our domain resolves to our server. With this configuration in place, we will be able to use Certbot's webroot plugin to obtain certificates for our domain.
  • location /: In this location block, we'll use a try_files directive to check for files that match individual URI requests. Instead of returning a 404 Not Found status as a default, however, we'll pass control to WordPress's index.php file with the request arguments.
  • location ~ \.php### Introduction[WordPress](https://wordpress.org/ "WordPress") is a free and open-source Content Management System (CMS) built on a [MySQL](https://www.mysql.com/ "MySQL")database with [PHP](https://www.php.net/ "PHP") processing. Thanks to its extensible plugin architecture and templating system, and the fact that most of its administration can be done through the web interface, WordPress is a popular choice when creating different types of websites, from blogs to product pages to eCommerce sites.Running WordPress typically involves installing a LAMP (Linux, Apache, MySQL, and PHP) or LEMP (Linux, Nginx, MySQL, and PHP) stack, which can be time-consuming. However, by using tools like Docker and Docker Compose, you can simplify the process of setting up your preferred stack and installing WordPress. Instead of installing individual components by hand, you can use *images*, which standardize things like libraries, configuration files, and environment variables, and run these images in *containers*, isolated processes that run on a shared operating system. Additionally, by using Compose, you can coordinate multiple containers — for example, an application and database — to communicate with one another.In this tutorial, you will build a multi-container WordPress installation. Your containers will include a MySQL database, an Nginx web server, and WordPress itself. You will also secure your installation by obtaining TLS/SSL certificates with Let's Encrypt for the domain you want associated with your site. Finally, you will set up a cronjob to renew your certificates so that your domain remains secure. ## PrerequisitesTo follow this tutorial, you will need: * A server running Ubuntu 18.04, along with a non-root user withsudoprivileges and an active firewall. For guidance on how to set these up, please see this Initial Server Setup guide. * Docker installed on your server, following Steps 1 and 2 of How To Install and Use Docker on Ubuntu 18.04. * Docker Compose installed on your server, following Step 1 of How To Install Docker Compose on Ubuntu 18.04. * A registered domain name. This tutorial will use example.com throughout. You can get one for free at Freenom, or use the domain registrar of your choice. * Both of the following DNS records set up for your server. You can follow this introduction to DigitalOcean DNS for details on how to add them to a DigitalOcean account, if that’s what you’re using: An A record withexample.compointing to your server's public IP address. An A record withwww.example.compointing to your server's public IP address.## Step 1 — Defining the Web Server ConfigurationBefore running any containers, our first step will be to define the configuration for our Nginx web server. Our configuration file will include some WordPress-specific location blocks, along with a location block to direct Let's Encrypt verification requests to the Certbot client for automated certificate renewals.First, create a project directory for your WordPress setup calledwordpress and navigate to it: mkdir wordpress && cd wordpressNext, make a directory for the configuration file: mkdir nginx-confOpen the file with nano or your favorite editor: nano nginx-conf/nginx.confIn this file, we will add a server block with directives for our server name and document root, and location blocks to direct the Certbot client's request for certificates, PHP processing, and static asset requests.Paste the following code into the file. Be sure to replace example.com with your own domain name: ~/wordpress/nginx-conf/nginx.conf server { listen 80; listen [::]:80;server_name example.com www.example.com;index index.php index.html index.htm;root /var/www/html;location ~ /.well-known/acme-challenge {allow all;root /var/www/html;} location / { try_files $uri $uri/ /index.php$is_args$args; }location ~ .php$ { try_files $uri =404; fastcgi_split_path_info ^(.+.php)(/.+)$; fastcgi_pass wordpress:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info;} location ~ /.ht { deny all; }location = /favicon.ico { log_not_found off; access_log off; } location = /robots.txt { log_not_found off; access_log off; allow all; } location ~* .(css|gif|ico|jpeg|jpg|js|png)$ { expires max; log_not_found off; }}Our server block includes the following information:Directives: * listen: This tells Nginx to listen on port 80, which will allow us to use Certbot's [webroot plugin](https://certbot.eff.org/docs/using.html#webroot "webroot plugin") for our certificate requests. Note that we are *not* including port 443yet — we will update our configuration to include SSL once we have successfully obtained our certificates. *server_name: This defines your server name and the server block that should be used for requests to your server. Be sure to replace example.comin this line with your own domain name. *index: The indexdirective defines the files that will be used as indexes when processing requests to your server. We've modified the default order of priority here, movingindex.phpin front of index.htmlso that Nginx prioritizes files calledindex.phpwhen possible. *root: Our rootdirective names the root directory for requests to our server. This directory,/var/www/html, is [created as a mount point](https://github.com/docker-library/wordpress/blob/07958d19ed465fb7fe50626be740d88a2c2260a7/php7.2/fpm-alpine/Dockerfile#L53 "created as a mount point") at build time by instructions in our [WordPress Dockerfile](https://github.com/docker-library/wordpress/blob/07958d19ed465fb7fe50626be740d88a2c2260a7/php7.2/fpm-alpine/Dockerfile "WordPress Dockerfile"). These Dockerfile instructions also ensure that the files from the WordPress release are mounted to this volume.Location Blocks: * location ~ /.well-known/acme-challenge: This location block will handle requests to the .well-knowndirectory, where Certbot will place a temporary file to validate that the DNS for our domain resolves to our server. With this configuration in place, we will be able to use Certbot's webroot plugin to obtain certificates for our domain. *location /: In this location block, we'll use a try_filesdirective to check for files that match individual URI requests. Instead of returning a 404Not Foundstatus as a default, however, we'll pass control to WordPress'sindex.phpfile with the request arguments. * ``: This location block will handle PHP processing and proxy these requests to ourwordpresscontainer. Because our WordPress Docker image will be based on thephp:fpm[ image](https://github.com/docker-library/php/blob/e63194a0006848edb13b7eff5a7f9d790d679428/7.2/alpine3.9/fpm/Dockerfile " image"), we will also include configuration options that are specific to the [FastCGI protocol](https://en.wikipedia.org/wiki/FastCGI "FastCGI protocol") in this block. Nginx requires an independent PHP processor for PHP requests: in our case, these requests will be handled by the php-fpmprocessor that's included with thephp:fpmimage. Additionally, this location block includes FastCGI-specific directives, variables, and options that will proxy requests to the WordPress application running in ourwordpresscontainer, set the preferred index for the parsed request URI, and parse URI requests. *location ~ /.ht: This block will handle .htaccessfiles since Nginx won't serve them. Thedeny_alldirective ensures that.htaccessfiles will never be served to users. *location = /favicon.ico, location = /robots.txt: These blocks ensure that requests to /favicon.icoand/robots.txtwill not be logged. *location ~* .(css|gif|ico|jpeg|jpg|js|png)### IntroductionWordPress is a free and open-source Content Management System (CMS) built on a MySQLdatabase with PHP processing. Thanks to its extensible plugin architecture and templating system, and the fact that most of its administration can be done through the web interface, WordPress is a popular choice when creating different types of websites, from blogs to product pages to eCommerce sites.Running WordPress typically involves installing a LAMP (Linux, Apache, MySQL, and PHP) or LEMP (Linux, Nginx, MySQL, and PHP) stack, which can be time-consuming. However, by using tools like Docker and Docker Compose, you can simplify the process of setting up your preferred stack and installing WordPress. Instead of installing individual components by hand, you can use images, which standardize things like libraries, configuration files, and environment variables, and run these images in containers, isolated processes that run on a shared operating system. Additionally, by using Compose, you can coordinate multiple containers — for example, an application and database — to communicate with one another.In this tutorial, you will build a multi-container WordPress installation. Your containers will include a MySQL database, an Nginx web server, and WordPress itself. You will also secure your installation by obtaining TLS/SSL certificates with Let's Encrypt for the domain you want associated with your site. Finally, you will set up a <a href="https://www.digitalocean.com/community/tutorials/how-to-schedule-routine-tasks-with-cron-and-anacron-on-a-vps" target="_blank">cron</a> job to renew your certificates so that your domain remains secure. ## PrerequisitesTo follow this tutorial, you will need: * A server running Ubuntu 18.04, along with a non-root user with sudo privileges and an active firewall. For guidance on how to set these up, please see this Initial Server Setup guide. * Docker installed on your server, following Steps 1 and 2 of How To Install and Use Docker on Ubuntu 18.04. * Docker Compose installed on your server, following Step 1 of How To Install Docker Compose on Ubuntu 18.04. * A registered domain name. This tutorial will use example.com throughout. You can get one for free at Freenom, or use the domain registrar of your choice. * Both of the following DNS records set up for your server. You can follow this introduction to DigitalOcean DNS for details on how to add them to a DigitalOcean account, if that’s what you’re using: An A record with example.com pointing to your server's public IP address. An A record with www.example.com pointing to your server's public IP address.## Step 1 — Defining the Web Server ConfigurationBefore running any containers, our first step will be to define the configuration for our Nginx web server. Our configuration file will include some WordPress-specific location blocks, along with a location block to direct Let's Encrypt verification requests to the Certbot client for automated certificate renewals.First, create a project directory for your WordPress setup called wordpress and navigate to it: mkdir wordpress && cd wordpressNext, make a directory for the configuration file: mkdir nginx-confOpen the file with nano or your favorite editor: nano nginx-conf/nginx.confIn this file, we will add a server block with directives for our server name and document root, and location blocks to direct the Certbot client's request for certificates, PHP processing, and static asset requests.Paste the following code into the file. Be sure to replace example.com with your own domain name: ~/wordpress/nginx-conf/nginx.conf
    server { listen 80; listen [::]:80;server_name example.com www.example.com;index index.php index.html index.htm;root /var/www/html;location ~ /.well-known/acme-challenge {allow all;root /var/www/html;} location / { try_files $uri $uri/ /index.php$is_args$args; }location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass wordpress:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info;} location ~ /\.ht { deny all; }location = /favicon.ico { log_not_found off; access_log off; } location = /robots.txt { log_not_found off; access_log off; allow all; } location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ { expires max; log_not_found off; }}Our server block includes the following information:Directives: * listen: This tells Nginx to listen on port 80, which will allow us to use Certbot's webroot plugin for our certificate requests. Note that we are not including port 443 yet — we will update our configuration to include SSL once we have successfully obtained our certificates. * server_name: This defines your server name and the server block that should be used for requests to your server. Be sure to replace example.com in this line with your own domain name. * index: The index directive defines the files that will be used as indexes when processing requests to your server. We've modified the default order of priority here, moving index.phpin front of index.html so that Nginx prioritizes files called index.php when possible. * root: Our root directive names the root directory for requests to our server. This directory, /var/www/html, is created as a mount point at build time by instructions in our WordPress Dockerfile. These Dockerfile instructions also ensure that the files from the WordPress release are mounted to this volume.Location Blocks: * location ~ /.well-known/acme-challenge: This location block will handle requests to the .well-known directory, where Certbot will place a temporary file to validate that the DNS for our domain resolves to our server. With this configuration in place, we will be able to use Certbot's webroot plugin to obtain certificates for our domain. * location /: In this location block, we'll use a try_files directive to check for files that match individual URI requests. Instead of returning a 404 Not Found status as a default, however, we'll pass control to WordPress's index.php file with the request arguments. * location ~ \.php### Introduction[WordPress](https://wordpress.org/ "WordPress") is a free and open-source Content Management System (CMS) built on a [MySQL](https://www.mysql.com/ "MySQL")database with [PHP](https://www.php.net/ "PHP") processing. Thanks to its extensible plugin architecture and templating system, and the fact that most of its administration can be done through the web interface, WordPress is a popular choice when creating different types of websites, from blogs to product pages to eCommerce sites.Running WordPress typically involves installing a LAMP (Linux, Apache, MySQL, and PHP) or LEMP (Linux, Nginx, MySQL, and PHP) stack, which can be time-consuming. However, by using tools like Docker and Docker Compose, you can simplify the process of setting up your preferred stack and installing WordPress. Instead of installing individual components by hand, you can use *images*, which standardize things like libraries, configuration files, and environment variables, and run these images in *containers*, isolated processes that run on a shared operating system. Additionally, by using Compose, you can coordinate multiple containers — for example, an application and database — to communicate with one another.In this tutorial, you will build a multi-container WordPress installation. Your containers will include a MySQL database, an Nginx web server, and WordPress itself. You will also secure your installation by obtaining TLS/SSL certificates with Let's Encrypt for the domain you want associated with your site. Finally, you will set up a cronjob to renew your certificates so that your domain remains secure. ## PrerequisitesTo follow this tutorial, you will need: * A server running Ubuntu 18.04, along with a non-root user withsudoprivileges and an active firewall. For guidance on how to set these up, please see this Initial Server Setup guide. * Docker installed on your server, following Steps 1 and 2 of How To Install and Use Docker on Ubuntu 18.04. * Docker Compose installed on your server, following Step 1 of How To Install Docker Compose on Ubuntu 18.04. * A registered domain name. This tutorial will use example.com throughout. You can get one for free at Freenom, or use the domain registrar of your choice. * Both of the following DNS records set up for your server. You can follow this introduction to DigitalOcean DNS for details on how to add them to a DigitalOcean account, if that’s what you’re using: An A record withexample.compointing to your server's public IP address. An A record withwww.example.compointing to your server's public IP address.## Step 1 — Defining the Web Server ConfigurationBefore running any containers, our first step will be to define the configuration for our Nginx web server. Our configuration file will include some WordPress-specific location blocks, along with a location block to direct Let's Encrypt verification requests to the Certbot client for automated certificate renewals.First, create a project directory for your WordPress setup calledwordpress and navigate to it: mkdir wordpress && cd wordpressNext, make a directory for the configuration file: mkdir nginx-confOpen the file with nano or your favorite editor: nano nginx-conf/nginx.confIn this file, we will add a server block with directives for our server name and document root, and location blocks to direct the Certbot client's request for certificates, PHP processing, and static asset requests.Paste the following code into the file. Be sure to replace example.com with your own domain name: ~/wordpress/nginx-conf/nginx.conf server { listen 80; listen [::]:80;server_name example.com www.example.com;index index.php index.html index.htm;root /var/www/html;location ~ /.well-known/acme-challenge {allow all;root /var/www/html;} location / { try_files $uri $uri/ /index.php$is_args$args; }location ~ .php$ { try_files $uri =404; fastcgi_split_path_info ^(.+.php)(/.+)$; fastcgi_pass wordpress:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info;} location ~ /.ht { deny all; }location = /favicon.ico { log_not_found off; access_log off; } location = /robots.txt { log_not_found off; access_log off; allow all; } location ~* .(css|gif|ico|jpeg|jpg|js|png)$ { expires max; log_not_found off; }}Our server block includes the following information:Directives: * listen: This tells Nginx to listen on port 80, which will allow us to use Certbot's [webroot plugin](https://certbot.eff.org/docs/using.html#webroot "webroot plugin") for our certificate requests. Note that we are *not* including port 443yet — we will update our configuration to include SSL once we have successfully obtained our certificates. *server_name: This defines your server name and the server block that should be used for requests to your server. Be sure to replace example.comin this line with your own domain name. *index: The indexdirective defines the files that will be used as indexes when processing requests to your server. We've modified the default order of priority here, movingindex.phpin front of index.htmlso that Nginx prioritizes files calledindex.phpwhen possible. *root: Our rootdirective names the root directory for requests to our server. This directory,/var/www/html, is [created as a mount point](https://github.com/docker-library/wordpress/blob/07958d19ed465fb7fe50626be740d88a2c2260a7/php7.2/fpm-alpine/Dockerfile#L53 "created as a mount point") at build time by instructions in our [WordPress Dockerfile](https://github.com/docker-library/wordpress/blob/07958d19ed465fb7fe50626be740d88a2c2260a7/php7.2/fpm-alpine/Dockerfile "WordPress Dockerfile"). These Dockerfile instructions also ensure that the files from the WordPress release are mounted to this volume.Location Blocks: * location ~ /.well-known/acme-challenge: This location block will handle requests to the .well-knowndirectory, where Certbot will place a temporary file to validate that the DNS for our domain resolves to our server. With this configuration in place, we will be able to use Certbot's webroot plugin to obtain certificates for our domain. *location /: In this location block, we'll use a try_filesdirective to check for files that match individual URI requests. Instead of returning a 404Not Foundstatus as a default, however, we'll pass control to WordPress'sindex.phpfile with the request arguments. * ``: This location block will handle PHP processing and proxy these requests to ourwordpresscontainer. Because our WordPress Docker image will be based on thephp:fpm[ image](https://github.com/docker-library/php/blob/e63194a0006848edb13b7eff5a7f9d790d679428/7.2/alpine3.9/fpm/Dockerfile " image"), we will also include configuration options that are specific to the [FastCGI protocol](https://en.wikipedia.org/wiki/FastCGI "FastCGI protocol") in this block. Nginx requires an independent PHP processor for PHP requests: in our case, these requests will be handled by the php-fpmprocessor that's included with thephp:fpmimage. Additionally, this location block includes FastCGI-specific directives, variables, and options that will proxy requests to the WordPress application running in ourwordpresscontainer, set the preferred index for the parsed request URI, and parse URI requests. *location ~ /.ht: This block will handle .htaccessfiles since Nginx won't serve them. Thedeny_alldirective ensures that.htaccessfiles will never be served to users. *location = /favicon.ico, location = /robots.txt: These blocks ensure that requests to /favicon.icoand/robots.txtwill not be logged. * ``: This block turns off logging for static asset requests and ensures that these assets are highly cacheable, as they are typically expensive to serve.For more information about FastCGI proxying, see [Understanding and Implementing FastCGI Proxying in Nginx](https://www.digitalocean.com/community/tutorials/understanding-and-implementing-fastcgi-proxying-in-nginx "Understanding and Implementing FastCGI Proxying in Nginx"). For information about server and location blocks, see [Understanding Nginx Server and Location Block Selection Algorithms](https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms "Understanding Nginx Server and Location Block Selection Algorithms").Save and close the file when you are finished editing. If you usednano, do so by pressing CTRL+X, Y, then ENTER.With your Nginx configuration in place, you can move on to creating environment variables to pass to your application and database containers at runtime. ## Step 2 — Defining Environment VariablesYour database and WordPress application containers will need access to certain environment variables at runtime in order for your application data to persist and be accessible to your application. These variables include both sensitive and non-sensitive information: sensitive values for your MySQL root password and application database user and password, and non-sensitive information for your application database name and host.Rather than setting all of these values in our Docker Compose file — the main file that contains information about how our containers will run — we can set the sensitive values in an .envfile and restrict its circulation. This will prevent these values from copying over to our project repositories and being exposed publicly.In your main project directory,~/wordpress, open a file called .env: nano .envThe confidential values that we will set in this file include a password for our MySQL root user, and a username and password that WordPress will use to access the database.Add the following variable names and values to the file. Remember to supply your own valueshere for each variable:~/wordpress/.env MYSQL_ROOT_PASSWORD=your_root_passwordMYSQL_USER=your_wordpress_database_userMYSQL_PASSWORD=your_wordpress_database_passwordWe have included a password for the root administrative account, as well as our preferred username and password for our application database.Save and close the file when you are finished editing.Because your .envfile contains sensitive information, you will want to ensure that it is included in your project's.gitignoreand.dockerignorefiles, which tell Git and Docker what files not to copy to your Git repositories and Docker images, respectively.If you plan to work with Git for version control, initialize your current working directory as a repository withgit init: git initThen open a .gitignore file: nano .gitignoreAdd .env to the file:~/wordpress/.gitignore .envSave and close the file when you are finished editing.Likewise, it's a good precaution to add .envto a.dockerignore file, so that it doesn't end up on your containers when you are using this directory as your build context.Open the file: nano .dockerignoreAdd .env to the file:~/wordpress/.dockerignore .envBelow this, you can optionally add files and directories associated with your application's development:~/wordpress/.dockerignore .env.gitdocker-compose.yml.dockerignoreSave and close the file when you are finished.With your sensitive information in place, you can now move on to defining your services in a docker-compose.ymlfile. ## Step 3 — Defining Services with Docker ComposeYourdocker-compose.ymlfile will contain the service definitions for your setup. A *service* in Compose is a running container, and service definitions specify information about how each container will run.Using Compose, you can define different services in order to run multi-container applications, since Compose allows you to link these services together with shared networks and volumes. This will be helpful for our current setup since we will create different containers for our database, WordPress application, and web server. We will also create a container to run the Certbot client in order to obtain certificates for our webserver.To begin, open thedocker-compose.yml file: nano docker-compose.ymlAdd the following code to define your Compose file version and db database service:~/wordpress/docker-compose.yml version: '3'services: db: image: mysql:8.0 container_name: db restart: unless-stopped env_file:.env environment:- MYSQL_DATABASE=wordpress volumes: - dbdata:/var/lib/mysql command: '--default-authentication-plugin=mysql_native_password' networks: - app-networkThe dbservice definition contains the following options: *image: This tells Compose what image to pull to create the container. We are pinning the mysql:8.0[ image](https://github.com/docker-library/mysql/blob/130bd8e46a3da1adfc1732a08c70673e20aa5977/8.0/Dockerfile " image") here to avoid future conflicts as the mysql:latestimage continues to be updated. For more information about version pinning and avoiding dependency conflicts, see the Docker documentation on Dockerfile best practices. *container_name: This specifies a name for the container. * restart: This defines the container restart policy. The default is no, but we have set the container to restart unless it is stopped manually. * env_file: This option tells Compose that we would like to add environment variables from a file called .env, located in our build context. In this case, the build context is our current directory. * environment: This option allows you to add additional environment variables, beyond those defined in your .envfile. We will set theMYSQL_DATABASEvariable equal towordpressto provide a name for our application database. Because this is non-sensitive information, we can include it directly in thedocker-compose.ymlfile. *volumes: Here, we're mounting a named volume called dbdatato the/var/lib/mysqldirectory on the container. This is the standard data directory for MySQL on most distributions. * command: This option specifies a command to override the default CMD instruction for the image. In our case, we will add an option to the Docker image's standard mysqldcommand, which starts the MySQL server on the container. This option,--default-authentication-plugin=mysql_native_password, sets the --default-authentication-pluginsystem variable tomysql_native_password, specifying which authentication mechanism should govern new authentication requests to the server. Since PHP and therefore our WordPress image won't support MySQL's newer authentication default, we must make this adjustment in order to authenticate our application database user. * networks: This specifies that our application service will join the app-networknetwork, which we will define at the bottom of the file.Next, below yourdbservice definition, add the definition for yourwordpress application service:~/wordpress/docker-compose.yml ... wordpress: depends_on: - db image: wordpress:5.1.1-fpm-alpine container_name: wordpress restart: unless-stoppedenv_file: .env environment: - WORDPRESS_DB_HOST=db:3306 - WORDPRESS_DB_USER=$MYSQL_USER - WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD - WORDPRESS_DB_NAME=wordpress volumes: - wordpress:/var/www/html networks: - app-networkIn this service definition, we are naming our container and defining a restart policy, as we did with the dbservice. We're also adding some options specific to this container: *depends_on: This option ensures that our containers will start in order of dependency, with the wordpresscontainer starting after thedbcontainer. Our WordPress application relies on the existence of our application database and user, so expressing this order of dependency will enable our application to start properly. *image: For this setup, we are using the 5.1.1-fpm-alpine[ WordPress image](https://github.com/docker-library/wordpress/blob/07958d19ed465fb7fe50626be740d88a2c2260a7/php7.2/fpm-alpine/Dockerfile " WordPress image"). As discussed in Step 1, using this image ensures that our application will have the php-fpmprocessor that Nginx requires to handle PHP processing. This is also analpineimage, derived from the [Alpine Linux project](https://alpinelinux.org/ "Alpine Linux project"), which will help keep our overall image size down. For more information about the benefits and drawbacks of usingalpineimages and whether or not this makes sense for your application, see the full discussion under the Image Variants section of the Docker Hub WordPress image page. *env_file: Again, we specify that we want to pull values from our .envfile, since this is where we defined our application database user and password. *environment: Here, we're using the values we defined in our .envfile, but we're assigning them to the variable names that the WordPress image expects:WORDPRESS_DB_USERandWORDPRESS_DB_PASSWORD. We're also defining a WORDPRESS_DB_HOST, which will be the MySQL server running on the dbcontainer that's accessible on MySQL's default port,3306. Our WORDPRESS_DB_NAMEwill be the same value we specified in the MySQL service definition for ourMYSQL_DATABASE: wordpress. * volumes: We are mounting a named volume called wordpressto the/var/www/htmlmountpoint [created by the WordPress image](https://github.com/docker-library/wordpress/blob/07958d19ed465fb7fe50626be740d88a2c2260a7/php7.2/fpm-alpine/Dockerfile#L53 "created by the WordPress image"). Using a named volume in this way will allow us to share our application code with other containers. * networks: We're also adding the wordpresscontainer to theapp-networknetwork.Next, below thewordpressapplication service definition, add the following definition for yourwebserver Nginx service:~/wordpress/docker-compose.yml ... webserver: depends_on:- wordpress image: nginx:1.15.12-alpine container_name: webserver restart: unless-stopped ports:- "80:80" volumes: - wordpress:/var/www/html - ./nginx-conf:/etc/nginx/conf.d - certbot-etc:/etc/letsencrypt networks: - app-networkAgain, we're naming our container and making it dependent on the wordpresscontainer in order of starting. We're also using analpineimage — the1.15.12-alpine[ Nginx image](https://github.com/nginxinc/docker-nginx/blob/e5123eea0d29c8d13df17d782f15679458ff899e/mainline/stretch/Dockerfile " Nginx image").This service definition also includes the following options: * ports: This exposes port 80to enable the configuration options we defined in ournginx.conffile in Step 1. *volumes: Here, we are defining a combination of named volumes and bind mounts: wordpress:/var/www/html: This will mount our WordPress application code to the /var/www/htmldirectory, the directory we set as theroot in our Nginx server block../nginx-conf:/etc/nginx/conf.d: This will bind mount the Nginx configuration directory on the host to the relevant directory on the container, ensuring that any changes we make to files on the host will be reflected in the container.certbot-etc:/etc/letsencrypt: This will mount the relevant Let's Encrypt certificates and keys for our domain to the appropriate directory on the container. And again, we've added this container to the app-networknetwork.Finally, below yourwebserverdefinition, add your last service definition for thecertbot service. Be sure to replace the email address and domain names listed here with your own information:~/wordpress/docker-compose.ymlcertbot: depends_on: - webserver image: certbot/certbot container_name: certbot volumes:- certbot-etc:/etc/letsencrypt - wordpress:/var/www/html command: certonly --webroot --webroot-path=/var/www/html --email [email protected] --agree-tos --no-eff-email --staging -d example.com -d www.example.comThis definition tells Compose to pull the certbot/certbot[ image](https://hub.docker.com/r/certbot/certbot/ " image") from Docker Hub. It also uses named volumes to share resources with the Nginx container, including the domain certificates and key in certbot-etcand the application code inwordpress.Again, we've used depends_onto specify that thecertbotcontainer should be started once thewebserverservice is running.We've also included acommandoption that specifies a subcommand to run with the container's defaultcertbotcommand. Thecertonly[ subcommand](https://certbot.eff.org/docs/using.html#certbot-command-line-options " subcommand") will obtain a certificate with the following options: * --webroot: This tells Certbot to use the webroot plugin to place files in the webroot folder for authentication. This plugin depends on the [HTTP-01 validation method](https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.2 "HTTP-01 validation method"), which uses an HTTP request to prove that Certbot can access resources from a server that responds to a given domain name. * --webroot-path: This specifies the path of the webroot directory. * --email: Your preferred email for registration and recovery. * --agree-tos: This specifies that you agree to ACME's Subscriber Agreement. * --no-eff-email: This tells Certbot that you do not wish to share your email with the Electronic Frontier Foundation (EFF). Feel free to omit this if you would prefer. * --staging: This tells Certbot that you would like to use Let's Encrypt's staging environment to obtain test certificates. Using this option allows you to test your configuration options and avoid possible domain request limits. For more information about these limits, please see Let's Encrypt's rate limits documentation. * -d: This allows you to specify domain names you would like to apply to your request. In this case, we've included example.comandwww.example.com. Be sure to replace these with your own domain.Below the certbot service definition, add your network and volume definitions:~/wordpress/docker-compose.yml ...volumes: certbot-etc: wordpress: dbdata: networks: app-network: driver: bridge Our top-level volumeskey defines the volumescertbot-etc, wordpress, and dbdata. When Docker creates volumes, the contents of the volume are stored in a directory on the host filesystem, /var/lib/docker/volumes/, that's managed by Docker. The contents of each volume then get mounted from this directory to any container that uses the volume. In this way, it's possible to share code and data between containers.The user-defined bridge network app-networkenables communication between our containers since they are on the same Docker daemon host. This streamlines traffic and communication within the application, as it opens all ports between containers on the same bridge network without exposing any ports to the outside world. Thus, ourdb, wordpress, and webservercontainers can communicate with each other, and we only need to expose port 80for front-end access to the application.The finisheddocker-compose.yml file will look like this:~/wordpress/docker-compose.yml version: '3'services: db: image: mysql:8.0 container_name: db restart: unless-stopped env_file: .env environment:- MYSQL_DATABASE=wordpress volumes: - dbdata:/var/lib/mysqlcommand: '--default-authentication-plugin=mysql_native_password' networks: - app-networkwordpress: depends_on: - db image: wordpress:5.1.1-fpm-alpine container_name: wordpress restart: unless-stoppedenv_file: .env environment: - WORDPRESS_DB_HOST=db:3306 - WORDPRESS_DB_USER=$MYSQL_USER - WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD - WORDPRESS_DB_NAME=wordpress volumes:- wordpress:/var/www/html networks: - app-networkwebserver: depends_on: - wordpress image: nginx:1.15.12-alpine container_name: webserver restart: unless-stopped ports:- "80:80" volumes: - wordpress:/var/www/html - ./nginx-conf:/etc/nginx/conf.d - certbot-etc:/etc/letsencryptnetworks: - app-networkcertbot: depends_on: - webserver image: certbot/certbot container_name: certbot volumes:- certbot-etc:/etc/letsencrypt - wordpress:/var/www/html command: certonly --webroot --webroot-path=/var/www/html --email [email protected] --agree-tos --no-eff-email --staging -d example.com -d www.example.comvolumes: certbot-etc: wordpress: dbdata:networks: app-network:driver: bridge Save and close the file when you are finished editing.With your service definitions in place, you are ready to start the containers and test your certificate requests. ## Step 4 — Obtaining SSL Certificates and CredentialsWe can start our containers with the docker-compose upcommand, which will create and run our containers in the order we have specified. If our domain requests are successful, we will see the correct exit status in our output and the right certificates mounted in the/etc/letsencrypt/livefolder on the webservercontainer.Create the containers withdocker-compose upand the-dflag, which will run thedb, wordpress, and webserver containers in the background: docker-compose up -dYou will see output confirming that your services have been created: Output Creating db ... doneCreating wordpress ... doneCreating webserver ... doneCreating certbot ... doneUsing docker-compose ps, check the status of your services: docker-compose psIf everything was successful, your db, wordpress, and webserverservices will beUpand thecertbotcontainer will have exited with a0 status message: Output Name Command State Ports ------------------------------------------------------------------------- certbot certbot certonly --webroot ... Exit 0 db docker-entrypoint.sh --def ... Up 3306/tcp, 33060/tcp webserver nginx -g daemon off; Up 0.0.0.0:80->80/tcp wordpress docker-entrypoint.sh php-fpm Up 9000/tcp If you see anything other than Upin theStatecolumn for thedb, wordpress, or webserverservices, or an exit status other than 0for thecertbotcontainer, be sure to check the service logs with thedocker-compose logs command: docker-compose logs service_nameYou can now check that your certificates have been mounted to the webservercontainer withdocker-compose exec: docker-compose exec webserver ls -la /etc/letsencrypt/liveIf your certificate requests were successful, you will see output like this: Output total 16 drwx------ 3 root root 4096 May 10 15:45 . drwxr-xr-x 9 root root 4096 May 10 15:45 .. -rw-r--r-- 1 root root 740 May 10 15:45 README drwxr-xr-x 2 root root 4096 May 10 15:45 example.comNow that you know your request will be successful, you can edit the certbotservice definition to remove the--stagingflag.Opendocker-compose.yml: nano docker-compose.ymlFind the section of the file with the certbotservice definition, and replace the--stagingflag in thecommandoption with the--force-renewalflag, which will tell Certbot that you want to request a new certificate with the same domains as an existing certificate. Thecertbot service definition will now look like this:~/wordpress/docker-compose.yml ... certbot: depends_on: - webserver image: certbot/certbot container_name: certbot volumes: - certbot-etc:/etc/letsencrypt - certbot-var:/var/lib/letsencrypt - wordpress:/var/www/html command: certonly --webroot --webroot-path=/var/www/html --email [email protected] --agree-tos --no-eff-email --force-renewal -d example.com -d www.example.com ...You can now run docker-compose upto recreate thecertbotcontainer. We will also include the--no-depsoption to tell Compose that it can skip starting thewebserver service, since it is already running: docker-compose up --force-recreate --no-deps certbotYou will see output indicating that your certificate request was successful: Output Recreating certbot ... done Attaching to certbot certbot | Saving debug log to /var/log/letsencrypt/letsencrypt.log certbot | Plugins selected: Authenticator webroot, Installer None certbot | Renewing an existing certificate certbot | Performing the following challenges: certbot | http-01 challenge for example.com certbot | http-01 challenge for www.example.com certbot | Using the webroot path /var/www/html for all unmatched domains. certbot | Waiting for verification... certbot | Cleaning up challenges certbot | IMPORTANT NOTES: certbot | - Congratulations! Your certificate and chain have been saved at: certbot | /etc/letsencrypt/live/example.com/fullchain.pem certbot | Your key file has been saved at: certbot | /etc/letsencrypt/live/example.com/privkey.pem certbot | Your cert will expire on 2019-08-08. To obtain a new or tweaked certbot | version of this certificate in the future, simply run certbot certbot | again. To non-interactively renew all of your certificates, run certbot | "certbot renew" certbot | - Your account credentials have been saved in your Certbot certbot | configuration directory at /etc/letsencrypt. You should make a certbot | secure backup of this folder now. This configuration directory will certbot | also contain certificates and private keys obtained by Certbot so certbot | making regular backups of this folder is ideal. certbot | - If you like Certbot, please consider supporting our work by: certbot | certbot | Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate certbot | Donating to EFF: https://eff.org/donate-le certbot | certbot exited with code 0With your certificates in place, you can move on to modifying your Nginx configuration to include SSL. ## Step 5 — Modifying the Web Server Configuration and Service DefinitionEnabling SSL in our Nginx configuration will involve adding an HTTP redirect to HTTPS, specifying our SSL certificate and key locations, and adding security parameters and headers.Since you are going to recreate the webserver service to include these additions, you can stop it now: docker-compose stop webserverBefore we modify the configuration file itself, let's first get the recommended Nginx security parameters from Certbot using curl: curl -sSLo nginx-conf/options-ssl-nginx.conf https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/options-ssl-nginx.confThis command will save these parameters in a file called options-ssl-nginx.conf, located in the nginx-conf directory.Next, remove the Nginx configuration file you created earlier: rm nginx-conf/nginx.confOpen another version of the file: nano nginx-conf/nginx.confAdd the following code to the file to redirect HTTP to HTTPS and to add SSL credentials, protocols, and security headers. Remember to replace example.comwith your own domain:~/wordpress/nginx-conf/nginx.conf server { listen 80; listen [::]:80; server_name example.com www.example.com; location ~ /.well-known/acme-challenge { allow all; root /var/www/html; } location / { rewrite ^ https://$host$request_uri? permanent; } }server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name example.com www.example.com; index index.php index.html index.htm; root /var/www/html; server_tokens off; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; include /etc/nginx/conf.d/options-ssl-nginx.conf; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always; # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # enable strict transport security only if you understand the implications location / { try_files $uri $uri/ /index.php$is_args$args; } location ~ .php$ { try_files $uri =404; fastcgi_split_path_info ^(.+.php)(/.+)$; fastcgi_pass wordpress:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } location ~ /.ht { deny all; } location = /favicon.ico { log_not_found off; access_log off; } location = /robots.txt { log_not_found off; access_log off; allow all; } location ~* .(css|gif|ico|jpeg|jpg|js|png)$ { expires max; log_not_found off; } }The HTTP server block specifies the webroot for Certbot renewal requests to the .well-known/acme-challengedirectory. It also includes a rewrite directive that directs HTTP requests to the root directory to HTTPS.The HTTPS server block enablessslandhttp2. To read more about how HTTP/2 iterates on HTTP protocols and the benefits it can have for website performance, please see the introduction to How To Set Up Nginx with HTTP/2 Support on Ubuntu 18.04.This block also includes our SSL certificate and key locations, along with the recommended Certbot security parameters that we saved to nginx-conf/options-ssl-nginx.conf.Additionally, we've included some security headers that will enable us to get A ratings on things like the SSL Labs and Security Headers server test sites. These headers include X-Frame-Options, X-Content-Type-Options, Referrer Policy, Content-Security-Policy, and X-XSS-Protection. The [HTTP ](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security "HTTP ")Strict Transport Security(HSTS) header is commented out — enable this only if you understand the implications and have assessed its "preload" functionality.Ourrootandindexdirectives are also located in this block, as are the rest of the WordPress-specific location blocks discussed in Step 1.Once you have finished editing, save and close the file.Before recreating thewebserverservice, you will need to add a443port mapping to yourwebserverservice definition.Open yourdocker-compose.yml file: nano docker-compose.ymlIn the webserver service definition, add the following port mapping:~/wordpress/docker-compose.yml ... webserver: depends_on: - wordpress image: nginx:1.15.12-alpine container_name: webserver restart: unless-stopped ports: - "80:80" - "443:443" volumes: - wordpress:/var/www/html - ./nginx-conf:/etc/nginx/conf.d - certbot-etc:/etc/letsencrypt networks: - app-networkThe docker-compose.yml file will look like this when finished:~/wordpress/docker-compose.yml version: '3'services: db: image: mysql:8.0 container_name: db restart: unless-stopped env_file: .env environment: - MYSQL_DATABASE=wordpress volumes: - dbdata:/var/lib/mysql command: '--default-authentication-plugin=mysql_native_password' networks: - app-network wordpress: depends_on: - db image: wordpress:5.1.1-fpm-alpine container_name: wordpress restart: unless-stopped env_file: .env environment: - WORDPRESS_DB_HOST=db:3306 - WORDPRESS_DB_USER=$MYSQL_USER - WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD - WORDPRESS_DB_NAME=wordpress volumes: - wordpress:/var/www/html networks: - app-network webserver: depends_on: - wordpress image: nginx:1.15.12-alpine container_name: webserver restart: unless-stopped ports: - "80:80" - "443:443" volumes: - wordpress:/var/www/html - ./nginx-conf:/etc/nginx/conf.d - certbot-etc:/etc/letsencrypt networks: - app-network certbot: depends_on: - webserver image: certbot/certbot container_name: certbot volumes: - certbot-etc:/etc/letsencrypt - wordpress:/var/www/html command: certonly --webroot --webroot-path=/var/www/html --email [email protected] --agree-tos --no-eff-email --force-renewal -d example.com -d www.example.comvolumes: certbot-etc: wordpress: dbdata:networks: app-network: driver: bridge Save and close the file when you are finished editing.Recreate the webserver service: docker-compose up -d --force-recreate --no-deps webserverCheck your services with docker-compose ps: docker-compose psYou should see output indicating that your db, wordpress, and webserver services are running: Output Name Command State Ports -------------------------------------------------------------------------------------------- certbot certbot certonly --webroot ... Exit 0 db docker-entrypoint.sh --def ... Up 3306/tcp, 33060/tcp webserver nginx -g daemon off; Up 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp wordpress docker-entrypoint.sh php-fpm Up 9000/tcpWith your containers running, you can now complete your WordPress installation through the web interface. ## Step 6 — Completing the Installation Through the Web InterfaceWith our containers running, we can finish the installation through the WordPress web interface.In your web browser, navigate to your server's domain. Remember to substitute example.com here with your own domain name: https://example.comSelect the language you would like to use:![](https://assets.digitalocean.com/articles/docker-wordpress/wp_language_select.png)After clicking Continue, you will land on the main setup page, where you will need to pick a name for your site and a username. It's a good idea to choose a memorable username here (rather than "admin") and a strong password. You can use the password that WordPress generates automatically or create your own.Finally, you will need to enter your email address and decide whether or not you want to discourage search engines from indexing your site:![](https://assets.digitalocean.com/articles/docker-wordpress/wp_main_setup.png)Clicking on Install WordPress at the bottom of the page will take you to a login prompt:![](https://assets.digitalocean.com/articles/docker-wordpress/wp_login.png)Once logged in, you will have access to the WordPress administration dashboard:![](https://assets.digitalocean.com/articles/docker-wordpress/wp_main_dash.png)With your WordPress installation complete, you can now take steps to ensure that your SSL certificates will renew automatically. ## Step 7 — Renewing CertificatesLet's Encrypt certificates are valid for 90 days, so you will want to set up an automated renewal process to ensure that they do not lapse. One way to do this is to create a job with the cronscheduling utility. In this case, we will create a cronjob to periodically run a script that will renew our certificates and reload our Nginx configuration.First, open a script calledssl_renew.sh: nano ssl_renew.shAdd the following code to the script to renew your certificates and reload your web server configuration. Remember to replace the example username here with your own non-root username: ~/wordpress/ssl_renew.sh #!/bin/bashCOMPOSE="/usr/local/bin/docker-compose --no-ansi"cd /home/sammy/wordpress/ $COMPOSE run certbot renew --dry-run && $COMPOSE kill -s SIGHUP webserverThis script first assigns the docker-composebinary to a variable calledCOMPOSE, and specifies the --no-ansioption, which will rundocker-composecommands without [ANSI control characters](https://vt100.net/docs/vt510-rm/chapter4.html "ANSI control characters"). It then changes to the~/wordpressproject directory and runs the followingdocker-composecommands: * docker-compose run: This will start a certbotcontainer and override thecommandprovided in ourcertbotservice definition. Instead of using thecertonlysubcommand, we're using therenewsubcommand here, which will renew certificates that are close to expiring. We've included the--dry-run``` option here to test our script.
  • <a href="https://docs.docker.com/compose/reference/kill/" target="_blank">docker-compose kill</a>: This will send a <a href="https://en.wikipedia.org/wiki/SIGHUP" target="_blank">SIGHUP</a> signal to the webserver container to reload the Nginx configuration. For more information on using this process to reload your Nginx configuration, please see this Docker blog post on deploying the official Nginx image with Docker.

Close the file when you are finished editing. Make it executable:

chmod +x ssl_renew.sh

Next, open your root crontab file to run the renewal script at a specified interval:

sudo crontab -e 

If this is your first time editing this file, you will be asked to choose an editor:

Output
no crontab for root - using an empty one

Select an editor. To change later, run 'select-editor'.
 1. /bin/nano    <---- easiest
 2. /usr/bin/vim.basic
 3. /usr/bin/vim.tiny
 4. /bin/ed

Choose 1-4 [1]:
...

At the bottom of the file, add the following line:

                                 crontab
...
*/5 * * * * /home/sammy/wordpress/ssl_renew.sh >> /var/log/cron.log 2>&1

This will set the job interval to every five minutes, so you can test whether or not your renewal request has worked as intended. We have also created a log file, cron.log, to record relevant output from the job.

After five minutes, check cron.log to see whether or not the renewal request has succeeded:

tail -f /var/log/cron.log

You should see output confirming a successful renewal:

Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**     (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
 /etc/letsencrypt/live/example.com/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**     (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

You can now modify the crontab file to set a daily interval. To run the script every day at noon, for example, you would modify the last line of the file to look like this:

                              crontab
...
0 12 * * * /home/sammy/wordpress/ssl_renew.sh >> /var/log/cron.log 2>&1

You will also want to remove the --dry-run option from your ssl_renew.sh script:

                            ~/wordpress/ssl_renew.sh
#!/bin/bash

COMPOSE="/usr/local/bin/docker-compose --no-ansi"

cd /home/sammy/wordpress/
$COMPOSE run certbot renew && $COMPOSE kill -s SIGHUP webserver

Your cron job will ensure that your Let's Encrypt certificates don't lapse by renewing them when they are eligible. You can also set up log rotation with the Logrotate utility to rotate and compress your log files.

Conclusion

In this tutorial, you used Docker Compose to create a WordPress installation with an Nginx web server. As part of this workflow, you obtained TLS/SSL certificates for the domain you want associated with your WordPress site. Additionally, you created a cron job to renew these certificates when necessary.

Originally published on https://www.digitalocean.com