Professional Documents
Culture Documents
Building PHP Applications in Docker 2017
Building PHP Applications in Docker 2017
2
Chapter 1. Introduction to Docker
Since its first release in 2013, Docker has become the container engine of choice for
many developers, and it may be replacing a virtual machine near you soon. This book
offers a step-by-step guide that will walk you through the process of building a real PHP
web application using Docker while explaining the basics of the platform along the way.
The application we build in this book will do many of the key things that PHP developers
need to do on a daily basis, including:
• Installing dependencies using Composer.
• Using a web framework (SlimPHP) for routing.
• Getting data from a third-party API.
• Saving data to a database.
• Securely setting environmental variables.
This book was written for seasoned PHP developers who want to learn to develop web
applications with Docker, learn how Docker works, and like learning by building real,
working applications. Once you've completed this book, you'll be ready for more
advanced Docker topics, and I’ve included some resources to help you at the end.
All the code I include in this book is open source and freely available on Github. If you
see any problems or you want to suggest an improvement, feel free to make a pull
request. Finally, check www.shiphp.com often for blog posts, courses, and new books
on both Docker and PHP.
What is Docker?
Docker is a platform for managing and running containers. Containers are like virtual
machines, but they don't actually emulate the whole operating system. Instead, all of
the containers you run share the same underlying kernel with the host machine, which
means that they're much lighter-weight than virtual machines. Because of this,
containers are very efficient, and most real-world applications run many containers at
once. Docker helps you link these containers together using networks of containers, and
helps you define your containers using Docker Compose configuration files. While this
book doesn’t cover these topics in detail, it will get you started, and more in-depth
courses are available at www.shiphp.com/courses.
3
Diagram 1: Docker Container vs. Virtual Machine
Why Docker?
PHP developers who are familiar with virtual machines or have used Vagrant will most
easily understand containers by comparing them with virtual machines. The important
difference between containers and virtual machines in practice is that you will rarely use
one single Docker container for your whole application. Instead, you will run your PHP
code in one container, your web server in another container, and your database in third
container. Some applications I've worked on use dozens of linked containers to operate!
If you haven't used a virtual machine before, Docker containers can still help you
improve your development workflow. Developers who work on teams that run PHP and
Apache natively on their operating system often run into the "works on my machine"
problem. One team member updates the web application and everything seems to work
4
fine, but then when the application is deployed to the server or run by another team
member, it mysteriously breaks. Docker solves these problems by allowing developers
to set up and share replicable development environments, and then test and deploy
applications in exactly the same state using containers.
At this point you may want to read a little more about Docker. While this book will walk
you through the process of building and running a PHP web application on Docker, it
does not attempt to cover the inner-workings of Docker, containers, or operating
system virtualization. You can read more on Docker and many of these related topics on
Docker's website.
5
Docker (available for Mac, Windows, or Linux) installed on your computer. Assuming
you've got those things, let's get started!
6
Chapter 2. Running a PHP Script in Docker
Before we start building our application, it's helpful to get an idea of how Docker works
by simply running a PHP script in Docker. Let's start by writing a classic Hello World!
script in PHP:
hello.php
<?php
You can run this script in a VM or on your laptop (assuming you have PHP installed) by
running php hellp.php from your terminal. You should see the output Hello World! just
as we typed it above.
7
Fortunately for us, we usually don't have to build our own images from scratch. Most
popular software platforms (including PHP) have images that are officially provided by
the developers of the software, or by groups of interested users. You will rarely need to
build a completely new image, but later we'll see how to extend an existing image by
writing your own Dockerfile.
Docker images can be built and stored on your host machine, or they can live in a
remote "registry". In addition to maintaining the core Docker platform, the Docker team
maintains a large registry called Docker Hub, where public images can be stored for free.
Most open source software teams host official images on Docker Hub, including PHP.
This indicates that Docker is pulling the version of the PHP image tagged latest. When
it's done, Docker will indicate that it has pulled the latest version by showing you a
status like this:
Status: Downloaded newer image for php:latest
The “latest” tag is a standard convention that most Docker images use for
the most up-to-date version of their software. Beware using “latest”
indiscriminately though as it will automatically track the “latest” version
even when there is a major version change.
Since our hello.php script is simple, it doesn't matter which version of PHP we use, but
what if we need to run an older version of PHP for an existing project? This is where
Docker truly shines as we just need to specify the PHP version when we run docker
pull. For example, to download the PHP 5.6 image, we just run:
8
We can use this method to get newer, and unreleased versions of PHP as well (assuming
there's a at least a Beta version on the PHP registry’s list). This makes running PHP in
Docker very helpful for developers who need to work with multiple versions of PHP on a
regular basis.
If everything was done correctly, you should see Hello World! in your command line.
You just ran your first PHP script in Docker!
9
mounting the current directory (using $(pwd)) from our terminal into the /app
directory in the new Docker container.
• php:latestThis indicates the image we’re using for this container. You could specify
another PHP image (eg: php:7.0 or php:5.6) to use a specific version of the
language.
• php /app/hello.php Finally, this is the command that Docker will run in the
container. Since we mounted our code in the /app directory on the container, we
have to run our script from that directory.
Now that you have a basic understanding of Docker and can run a PHP script within
containers, it's time to build something a little more useful and interesting. This might
also be a good time to take a break and read up on some of the core Docker concepts in
their documentation. When you’re ready, read on to start building a PHP web
application in Docker.
10
Chapter 3. Creating a SlimPHP Application
In the rest of this book, we'll focus on our web application: a weather checker. We'll use
the MetaWeather API to search for a location and get the weather there. When we find
results, we'll save them to the database so that future requests will get the cached
version of the results.
I chose the MetaWeather API because it is free and does not require an
API key. There are other weather APIs you can use for free, but most
require you to sign up and get a key.
Our application will provide a very simple REST API that we'll use to interact with the
service. We'll create two endpoints:
• GET /locations/:location_id - Gets the weather from the database (if it exists) or
MetaWeather if a cached version does not.
• DELETE /locations/:location_id Deletes the cached version of the MetaWeather
results from the database. The next call to GET weather for this location will come
from the MetaWeather API.
Before we get to the application logic, let's start simple and get a fresh installation of
SlimPHP installed and running using Docker.
11
$ cd weather-app
If you view the weather-app/ directory, you'll see two files (composer.json and
composer.lock) and one directory (vendor/):
$ ls
composer.json composer.lock vendor
12
index.php
<?php require 'vendor/autoload.php';
// Declare routes
$app->get('/locations/{id}', function ($request, $response, $args) {
return $response->withStatus(200)->write("Location {$args['id']} retrieved.
");
});
13
Running Our Application for the First Time
Now we can run our application in a Docker container just to try it out. There will be two
endpoints that won't really do anything, but at least it will let us verify that we're on
track before we continue expanding the application. From the terminal, run:
$ docker run --rm -p 38000:80 -v $(pwd):/var/www/html php:apache
You've just successfully run your first PHP web application in Docker!
14
if you don't specify one. In the case of the PHP Apache image, the default command
runs a shell script that starts Apache. This script is exactly what we want, so there's
no reason to change it.
At this point, your terminal will show any Apache requests that come in, so when you
loaded the first URL, you probably saw something like:
172.17.0.1 - - [21/Aug/2017:18:35:33 +0000] "GET /index.php/locations/1 HTTP/1.
1" 200 250 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537
.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36"
You can stop and exit the terminal by sending a "break" signal to the terminal. On Macs,
this is done by holding control and pressing c.
Stopping a container
Immediately after starting the container in detached mode, your terminal will show the
full ID of the new container - something like
a403bf1018222a42baafffbc513f6749b6c3bbec5a19c4b3adf55e89bd23fb77 . If you want to
stop this container, you can tell Docker using the docker stop command with the
container's ID. For example:
$ docker stop a403bf1018222a42baafffbc513f6749b6c3bbec5a19c4b3adf55e89bd23fb77
Because typing that whole ID is cumbersome, Docker allows you to just type the first
three or more characters if you prefer:
$ docker stop a40
Naming containers
Finally, I recommend naming your containers. We will do this for many later examples in
this book as it is much easier to remember a container by name than by the randomly
assigned IDs, plus IDs are random, so each time you run a new version of the container,
it gets a new ID. Names can be given out many times as long as there's not already a
container with the same name. To name our new application container we could re-
create it with the --name flag passed in:
15
$ docker run -d --rm --name=weather-app -p 38000:80 -v $(pwd):/var/www/html php
:apache
There are many more options available when using the docker run command, so you
may want to read over the documentation in more detail. We will cover some of these
options as we develop the rest of our application.
This command will list all running containers. You can also see stopped containers by
adding the -a flag.
If any containers are running, then use docker stop <ID> to stop them before we
continue on.
Adding Guzzle
While you can get data from MetaWeather's API using PHP's built in cURL adapter, I
prefer Guzzle as it takes less work to make API calls. Plus, this will give us an opportunity
to install a new package using Composer. Open up the composer.json file that was
automatically created when we set up Slim, and add a line to the require block for
Guzzle:
{
"require": {
"slim/slim": "^3.0",
"guzzlehttp/guzzle": "~6.0"
}
}
The container will install the new packages and update your composer.lock file. During
this process, you should see some output like this in the terminal:
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 3 installs, 0 updates, 0 removals
- Installing guzzlehttp/promises (v1.3.1): Downloading (100%)
- Installing guzzlehttp/psr7 (1.4.2): Downloading (100%)
16
- Installing guzzlehttp/guzzle (6.3.0): Downloading (100%)
guzzlehttp/guzzle suggests installing psr/log (Required for using the Log middl
eware)
Writing lock file
Generating autoload files
17
What's going on here?
We've made some updates - mostly to the get('/locations/{id}', ...) endpoint. Let's
take a look at what's new here:
• $container = new \Slim\Container([...]); If you're not familiar with Slim's
Dependency Injection system, you can read up on it here. Since this book isn't
focused on dependency injection, I'll just say that this injects Guzzle into the
application so we can use it in any of our routes by calling $this->http. It makes our
code more concise, and allows us to mock the library if we add tests to the
application later.
• $result = $this->http-
>get("https://www.metaweather.com/api/location/{$args['id']}")... This is
where we make the call to MetaWeather through Guzzle. As you can see, we're
simply passing the locationId from our Slim route into the API endpoint provided by
MetaWeather. We also call getBody() and getContents() since we just want to get
the response as a string rather than a stream.
• return $response->withStatus(200)->withJson(json_decode($result)); Finally, we
respond with a 200 response code and withJson(...). Slim provides this method to
automatically set JSON response headers, but because the response from
MetaWeather came back as a JSON string, we have to run json_decode on it before
re-encoding it into JSON.
Testing it out
Our application is ready to pass a real location ID to MetaWeather's API and return
some data. First, find a location ID using this WOEID lookup tool. I'm using my home
town of Chicago's ID for testing: 2379574.
Now, let's run the Docker container again:
$ docker run -d --rm --name=weather-app -p 38000:80 -v $(pwd):/var/www/html php
:apache
18
"weather_state_name": "Heavy Rain",
"weather_state_abbr": "hr",
"wind_direction_compass": "SSW",
"created": "2017-08-21T17:23:20.408640Z",
"applicable_date": "2017-08-21",
"min_temp": 22.711666666666662,
"max_temp": 29.093333333333334,
"the_temp": 28.166666666666668,
"wind_speed": 5.319549422227524,
"wind_direction": 201.1261629987385,
"air_pressure": 1012.975,
"humidity": 74,
"visibility": 14.841140240992603,
"predictability": 77
},
...
],
"time": "2017-08-21T14:31:16.860660-05:00",
"sun_rise": "2017-08-21T06:05:09.010298-05:00",
"sun_set": "2017-08-21T19:42:46.959306-05:00",
"timezone_name": "LMT",
"parent": {
"title": "Illinois",
"location_type": "Region / State / Province",
"woeid": 2347572,
"latt_long": ""
},
"sources": [
{
"title": "BBC",
"slug": "bbc",
"url": "http://www.bbc.co.uk/weather/",
"crawl_rate": 180
},
...
],
"title": "Chicago",
"location_type": "City",
"woeid": 2379574,
"latt_long": "41.884151,-87.632408",
"timezone": "US/Central"
}
Your Dockerized PHP application is now returning real data from an external data source
and being served in Apache, but you'll probably notice that it's not exactly the speediest
application in the world (mine clocked in at 2.9 seconds page load time!).
MetaWeather is a free service, and not intended to be blazingly fast around the world.
In order to fix that, we're going to save responses from MetaWeather in our own MySQL
database and serve those saved responses up when we can. This will greatly improve
19
performance, but will also show us how to add a database to a PHP application with
Docker.
20
Chapter 4. Connecting to the Database
Before we can connect a database, we need to make sure that our PHP container has all
the necessary extensions installed. By default, the PHP Docker image from Docker Hub is
pretty lightweight, so it doesn't include many PHP extensions or Linux packages you
might need. This tradeoff between smaller and more flexible images is one you'll have
to make based on your project priorities, but since we know we'll need MySQL for this
project, let's extend the base PHP image with the required extensions.
The base PHP images are usually a good starting point, but I’ve also
started keeping some PHP images with more common extensions enabled
in Github. Check out this repository for more.
• RUN docker-php-source extract... This is the line that adds the PHP extensions we
need. In our case, we just want to use mysqli functions for our database. Since this
is such a common extension, the PHP image has methods for adding it automatically
when we extend the base image with these lines.
21
You can learn more about creating PHP Dockerfiles in our courses or you
can read the official Docker documentation for more options for extending
existing PHP images.
When you run this command, you will see a bunch of output as Docker builds your
image, ending with something like:
Removing intermediate container 80b3f79714b2
Successfully built 8dc1f8c175a1
Successfully tagged shiphp/weather-app:latest
22
database, we need to link it to an active database container. Let's start a new MySQL
container so we can link our web application to it:
$ docker run -d --rm --name weather-db -e MYSQL_USER=admin -e MYSQL_DATABASE=we
ather -e MYSQL_PASSWORD=p23l%v11p -e MYSQL_RANDOM_ROOT_PASSWORD=true mysql:5.7
23
If you are planning on using a database other than MySQL (like Postgres or
Mongo), check out the courses on our website. Other database and
caching tools are covered in more detail there.
You should see a line like root@35c7ad5a574d:/# (with your container's ID) on your
terminal, indicating that you're now logged in to the running container. From within the
container (not on your host machine), run:
$ mysql --user=admin --password
Enter password:
Enter the password you chose for the MySQL container you created above (in this
example it was p23l%v11p), and hit return. You'll see some information about MySQL
like this:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 5.7.19 MySQL Community Server (GPL)
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
Finally, let's just check that our database was created using the show databases
command:
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
24
| weather |
+--------------------+
2 rows in set (0.01 sec)
25
Exiting MySQL and the Container
We’re done with the MySQL container for now, so let’s exit and stop the container:
• To exit the MySQL CLI, type \q and then press return.
• Exit the container by typing exit and then pressing return.
• Stop the container by typing docker stop weather-db and then return again.
The whole command line output should look something like this when you're done:
mysql> \q
Bye
root@b01aff352dd7:/# exit
exit
$ docker stop weather-db
weather-db
We have now proven that our MySQL container runs and that we can log in to access
the database, but how will we keep data saved in the container? What happens when
we stop this container? Persistence is important in real-world applications, so in the
next section, we'll dive into keeping our data even after our container is long gone.
26
To start the MySQL container with the database saved to our host system, navigate to
your new weather-app project directory and type:
$ docker run -d --rm --name weather-db -e MYSQL_USER=admin -e MYSQL_DATABASE=we
ather -e MYSQL_PASSWORD=p23l%v11p -e MYSQL_RANDOM_ROOT_PASSWORD=true -v $(pwd)/
.data:/var/lib/mysql mysql:5.7
You should see files and folders listed out like this:
. ib_buffer_pool private_key.pem
.. ib_logfile0 public_key.pem
auto.cnf ib_logfile1 server-cert.pem
ca-key.pem ibdata1 server-key.pem
ca.pem ibtmp1 sys
client-cert.pem mysql weather
client-key.pem performance_schema
Don't worry about what each of those mean (although you can read up on MySQL
internals if you're interested). At this point, we just care that MySQL is now using data
from our host machine in the container, allowing us to keep the database data even
after the container is shut down.
Be sure to add the .data directory to your .gitignore file so that your
database isn’t added to version control.
27
Diagram 3: Weather Application Database
If the MySQL container is not running, then start it using the method above that mounts
data in a volume:
$ docker run -d --rm --name weather-db -e MYSQL_USER=admin -e MYSQL_DATABASE=we
ather -e MYSQL_PASSWORD=p23l%v11p -e MYSQL_RANDOM_ROOT_PASSWORD=true -v $(pwd)/
.data:/var/lib/mysql mysql:5.7
Log into the container and into the MySQL CLI. This time, we're going to do it in just one
step:
$ docker exec -it weather-db mysql --user=admin --password=p23l%v11p weather
Next, we're going to run a SQL command to create the database table we outlined
above:
mysql> CREATE TABLE locations (id VARCHAR(64) NOT NULL, weather JSON NULL, last
_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
It should tell you Query OK, but you can check by running mysql> SHOW TABLES;. Exit the
container and MySQL CLI by typing \q.
28
Saving to the Database from our PHP Application
Now that our database is ready to store weather data and it will keep that data even
after the container is removed, we need to update our PHP application to connect to
and store weather results in the database.
Initially, the index.php file was getting data directly from the MetaWeather API, but now
that we have a storage mechanism in place (ie: our database), let's get the application
to save data to the database for repeat requests.
index.php
<?php require 'vendor/autoload.php';
29
// Instantiate the App object
$app = new \Slim\App($container);
// If location found, then show it from the DB, otherwise, query MetaWeathe
r
if ($results->num_rows > 0) {
$result = $results->fetch_assoc()['weather'];
} else {
$result = $this->http->get("https://www.metaweather.com/api/location/{$
id}")
->getBody()
->getContents();
$cleanResult = $this->mysql->real_escape_string($result);
if (!$this->mysql->query("INSERT into locations (id, weather) VALUES ('
{$id}', '{$cleanResult}')")) {
throw new Exception("Location could not be updated.");
}
}
30
What's going on here?
We've added two important things to our index.php file above. First, we updated the
$container variable:
Guzzle is the same, but we've also added an element to the array named 'mysql'. This
callback instantiates MySQLi with hard-coded database credentials.
Hard-coding your database credentials like this is not a good idea in a real
application. We'll cover a more secure way to include credentials via
environmental variables in Docker in the last section of this chapter. In the
meantime, don't commit this to a public repository unless you plan on
changing your database password.
The other major update to the file is the GET /locations endpoint:
$app->get('/locations/{id}', function ($request, $response, $args) {
// If location found, then show it from the DB, otherwise, query MetaWeathe
r
if ($results->num_rows > 0) {
$result = $results->fetch_assoc()['weather'];
} else {
$result = $this->http->get("https://www.metaweather.com/api/location/{$
31
id}")
->getBody()
->getContents();
$cleanResult = $this->mysql->real_escape_string($result);
if (!$this->mysql->query("INSERT into locations (id, weather) VALUES ('
{$id}', '{$cleanResult}')")) {
throw new Exception("Location could not be updated.");
}
}
While the comments in the code may help, it's worth adding some more detail in case
you're not familiar with MySQLi:
• $id = $this->mysql->real_escape_string($args['id']); First, we escape the id
passed in through the endpoint's $args variable. This is a good idea anytime you're
taking user or third-party input as they might accidentally (or intentionally) inject
SQL statements that break your code.
• $results = $this->mysql->query("SELECT * FROM locations WHERE id='{$id}'");
While I would typically use an ORM for a real application, this simple two-endpoint
example didn't seem to warrant it. Instead, we're using MySQLi - a PHP extension
for accessing MySQL - to query the database. This allows us to write raw SQL with
PHP variables embedded.
• if ($results->num_rows > 0) {...} If we find at least one result for this location
ID, we should return the result from the database. If not, we'll continue on...
• else { ... $this->mysql->query("INSERT into locations (id, weather) VALUES
('{$id}', '{$cleanResult}')") ... If we did not find a result for this location ID in
the database, we're getting it from the MetaWeather API, escaping the JSON string,
and then inserting the result into the database.
• return $response->withStatus(200)->withJson(json_decode($result)); Just as we
did before, we're returning any result as JSON to the user.
32
What's going on here?
There are two new parts to this docker run command:
• --link weather-db This links the container named weather-db to this container. You
can also give the linked container an alias for use within the PHP container, but
we're not going to need to do that here.
• shiphp/weather-app Instead of using the php:apache image we used in the previous
chapter, we're using the new Docker image we built at the beginning of this
chapter. The reason for this is that we need the PHP MySQLi extension that we
added in our custom Dockerfile.
Now that the PHP container is up and running, you should be able to navigate to a
location ID like the following:
GET http://localhost:38000/index.php/locations/2487956
The first time you load this URL, it will probably take a second or two to load, but if you
refresh the page, you should see a dramatically faster result. Mine loaded in less than
150 ms! That's thanks to the caching we just set up by saving the result in the database.
Deleting Data
In order to remove data from the database, we'll add a second endpoint. Add the
following just after the $app->get(...); endpoint to allow users to delete a location
from the database:
33
index.php (partial)
...
Now you can use cURL or Postman to make a DELETE call to the same location endpoint
you did above. For example:
DELETE http://localhost:38000/index.php/locations/2487956
You should see a message Location 2487956 deleted. and now when you make a GET
request to that URL again, you'll wait longer as the result must come from the
MetaWeather API.
34
// Create a new Container
$container = new \Slim\Container([
// Add Guzzle as 'http'
'http' => function () {
return new GuzzleHttp\Client();
},
// Add mysqli as 'mysql'
'mysql' => function () {
$mysqli = new mysqli(
getenv('DATABASE_HOST'),
getenv('DATABASE_USER'),
getenv('DATABASE_PASSWORD'),
getenv('DATABASE_NAME')
);
if ($mysqli->connect_errno) {
echo "Failed to connect to MySQL: " . $mysqli->connect_error;
exit;
} else {
return $mysqli;
}
},
]);
Now you can safely share your PHP code without revealing your database login
credentials.
35
If you don’t want to have to type long commands like this in every time
you run your container, you can use Docker Compose and/or an .env file
instead. This is covered in detail the courses at shiphp.com/courses.
Finally, you can see the environmental variables your container is using:
$ docker inspect weather-app
The JSON returned from this command gives you a lot of information about a running
container, but for this example, we just care about the array at Config.Env:
[
"DATABASE_HOST=weather-app",
"DATABASE_USER=admin",
"DATABASE_PASSWORD=p23l%v11p",
"DATABASE_NAME=weather",
...
],
...
If everything worked correctly, you should now be able to access the application as
before. In the next chapter, we'll take a quick look at how we might improve this
application using advanced Docker topics. If you want to take a look at the complete
source code for this app, it's available on Github.
36
Chapter 5. Next Steps
At this point, our application is working and we can retrieve weather results from the
API or from our database cache, but there's still a lot more to learn. Building one demo
application won't make you an expert with Docker, so let's take a look at some things
we might do to continue to improve this application and learn more about Docker.
These topics and more are covered in our courses on shiphp.com, or you
can skip down to the end where we’ve included some other free resources
for learning about Docker.
Further Development
Clean up long Docker commands
Right now, starting and stopping our container is an arduous process and quite prone to
error. I'm guessing you don't want to type docker run -d --rm --name=weather-app -p
38000:80 -v $(pwd):/var/www/html --link weather-db -e DATABASE_HOST='weather-
db' -e DATABASE_USER='admin' -e DATABASE_PASSWORD='p23l%v11p' -e
DATABASE_NAME='weather' shiphp/weather-app every time you want to start editing your
code, but you can pretty easily avoid this by writing npm scripts or bash commands.
Docker Compose
Another way to clean up our Docker workflow is to use Docker Compose. Compose will
allow us to write a single configuration file that starts all of our containers and links
them together automatically. No more typing two or three commands just to get our
app running - plus, a Docker Compose file can be used to host our containers in a Docker
Swarm.
Hosting
This walkthrough has focused on local development with Docker, but you can also host
highly scalable and available web applications in Docker containers. There's no end to
the number of ways you can configure applications to run in the cloud, but look into
Docker Swarm, Kubernetes, Amazon EC2, and Hyper.sh for just a few options. These are
covered in more detail in our PHP Developer courses for Docker.
37
Accessing our database externally
Right now, our application's database is only accessible from the container, but what if
we want to browse it with a tool like Datagrip, phpMyAdmin, or Sequel Pro? We'll have
to expose a port and consider the security implications of doing so.
Resources
While this book doesn’t cover the above topics in detail, there are many great free
resources available for learning Docker in more depth. Some of them include:
• The official Docker Documentation - The documentation is a bit dry, but you should
definitely bookmark it as a reference. I find myself keeping this in an open tab
almost all the time.
• Stack Overflow - Very specific questions can be found here, but some of the more
popular questions will also reveal helpful best practices in their answers.
• The New Stack's Docker Book Series - Good for an overview of terminology and
tools available for developing with Docker. This book series is pretty high-level.
In addition, we regularly post Docker tips and tutorials on the shiphp.com blog. Posts
there relate to building PHP applications on Docker using specific frameworks like
Wordpress or Laravel as well as general PHP topics and updates on the language.
Final Words
I hope that reading and working through this book has piqued your interest in Docker
and given you the confidence to start using it regularly for local development. I have
38
found that the best way for me to learn something new is to just start building things,
and Docker has been no different. When I decided that Docker was how I would build
PHP apps, I uninstalled my virtual machine and went all-in. While you may not be ready
to do that just yet, forcing yourself to solve problems with Docker will make you get
better at it.
Finally, if you have feedback for this book, questions for me, or you’re interested in PHP
or Docker courses or training, my contact information is available at shiphp.com. Thanks
for reading!
39
About the Author
Karl Hughes has been building PHP web applications for education technology
companies since 2011. He went to the University of Tennessee and graduated with a
major in Mechanical Engineering, a minor in Business Administration, and a passion for
writing both prose and software.
In addition to blogging at www.karllhughes.com and www.shiphp.com he has
contributed to Codeship’s Blog, php[architect] magazine, and regularly speaks at
meetups and conferences. He currently lives in Chicago and works as the Chief
Technology Officer at The Graide Network.
40