Once you have created the first configuration of your basic Docker stack, it is time to move the Symfony’s files into Docker’s web server service.
This will make us able to run the symfony application from inside the web server container, getting the real benefits of using a system as Docker (same environment on any machine, above all).
So, we need a way to move in the container all the files required to make our project run.
How do we do this?
The answer to our question is a really simple word: “Dockerfile” (never heard of it? Ok, it is a not so simple word! But it is simple to use it anyway! 🙂 )
From the Docker documentation about “Dockerfile”:
Docker can build images automatically by reading the instructions from a Dockerfile
A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image.
Using docker build users can create an automated build that executes several command-line instructions in succession.
So, basically, all what you write in the console to configure your environment has to be put in a Dockerfile to automate the process.
Anytime someone wants to configure the environment to run our app, (s)he will not have to take care of the details as they are all in the Dockerfile: easy and fast!
So, let’s prepare our app to use Dockerfiles.
Create the folder structure and the Dockerfile
First of all we need to create some folders.
So, in the root of your app, create a folder called docker
, inside it create another folder called build
and inside it another folder called apache
, then inside it create a file called Dockerfile
.
The final folder structure will be like this:
The next steps are:
- Put in the
Dockerfile
the commands required to build theapache
container; - Configure
docker-compose.yaml
to use this newDockerfile
.
As first thing, instruct the Dockerfile
about the Apache image we want to use:
# docker/build/apache/Dockerfile
FROM httpd:2.4
The httpd:2.4
is exactly the same we used in our docker-compose.yaml
file.
Now, edit the docker-compose.yaml
file so it will use this Dockerfile
: we will do this using the build
key node:
version: '3.7'
services:
# "php" was "language" in previous example
...
# Configure the database
...
# Configure Apache
apache:
# image: httpd:2.4 <- Remove this node
build: docker/build/apache
ports:
- "8100:80"
Once edited the docker-compose.yaml
file, first run docker-compose down
to stop all the containers, then run docker-compose build
:
MacBook-Pro-di-Aerendir:app-aragog-www Aerendir$ docker-compose down
Stopping app-aragog-www_mysql_1 ... done
Stopping app-aragog-www_apache_1 ... done
Removing app-aragog-www_php_1 ... done
Removing app-aragog-www_mysql_1 ... done
Removing app-aragog-www_apache_1 ... done
Removing network app-aragog-www_default
MacBook-Pro-di-Aerendir:app-aragog-www Aerendir$ docker-compose build
php uses an image, skipping
mysql uses an image, skipping
Building apache
Step 1/1 : FROM httpd:2.4
---> fb2f3851a971
Successfully built fb2f3851a971
Successfully tagged app-aragog-www_apache:latest
As you can see, the php
and mysql
containers are skipped as they use an image, while the apache
container is built as it uses a Dockerfile
.
Try to start the containers with docker-compose up -d
:
MacBook-Pro-di-Aerendir:app-aragog-www Aerendir$ docker-compose up -d
Creating network "app-aragog-www_default" with the default driver
Creating app-aragog-www_mysql_1 ... done
Creating app-aragog-www_php_1 ... done
Creating app-aragog-www_apache_1 ... done
And access the URL http://127.0.0.1:8100
: you see the message “It works!”.
Ok, the first step is done: the Apache web server is continuing to work also if we are using a different method to configure it.
Now we need to do the second step: add the Symfony files to the document root of Apache.
To do this, we first need to understand what is the Docker Context as it will be extremely useful.
The Docker Context: what is it, why we need to understand it and how to use it
Before diving in the files and their copy in the container, we need to understand what is Docker Context.
From the documentation about build
:
The
docker build
command builds Docker images from a Dockerfile and a “context”.A build’s context is the set of files located in the specified
PATH
orURL
.The build process can refer to any of the files in the context.
For example, your build can use a
COPY
instruction to reference a file in the context.
So, to copy our Symfony files to the container, we need to make them available in the context.
We are using a docker-compose.yaml
file, so we need to understand how to specify the context when using it.
The solution? The context
key of the build
node.
The documentation says:
Either a path to a directory containing a Dockerfile, or a url to a git repository.
When the value supplied is a relative path, it is interpreted as relative to the location of the Compose file.
This directory is also the build context that is sent to the Docker daemon.
But there is more: we can also set a specific Dockerfile
to use when setting the context.
This is useful as because
By default the docker build command will look for a Dockerfile at the root of the build context
So, trying to recap what we have read until now in the documentation:
- We can copy or add files in the container from our machine only if they are in the “context”;
- Using the
build
key in thedocker-compose.yaml
file with a path (build: path/to/folder
):- Uses the
Dockerfile
found in this path; - Uses the content of this path as “context” (so we can access only files in it)
- Uses the
- In our
docker-compose.yaml
file, we can:- Specify a specific “context” to use to build the service/container;
- Specify a specific
Dockerfile
to use to build the service/container.
Interesting, isn’t it? Let’s put all those information at work to understand how to copy our Symfony files to the Apache container.
As usual, we will start small.
Trying to copy only a single file test.txt
First, we need a file to copy: create an empty file called test.txt
and put it in the root directory of the project: we will try to copy it in the container.
Now, let’s edit our docker-compose.yaml
file to specify the desired context and Dockerfile
:
# docker-compose.yaml
version: '3.7'
services:
# "php"
...
# Configure the database
...
# Configure Apache
apache:
# build: docker/build/apache <- Remove this node
# And recreate it as follows
build:
context: .
dockerfile: docker/build/apache/Dockerfile
ports:
- "8100:80"
As you can see, we removed the path to the apache
folder and, instead, added two more keys:
context
, that sets the context to the current folder (that is the root folder, as thedocker-compose.yaml
file is in the root);dockerfile
, that specifies whichDockerfile
to use to build the container/service that runs Apache
This has two effects:
- Sets the Docker Context to our root folder as the
docker-compose.yaml
file is in the root (so we can now use – so, we can copy, too – any file in our root folder); - Explicitly specifies the
Dockerfile
to use, without relying on the auto-discovery capabilities of Docker.
Before this modification, instead, the context was the folder docker/build/apache
(and so we were able to copy only files and folders in that folder) and the Dockerfile
was found thanks to the auto-discovery capabilities of Docker in finding Dockerfile
s in the provided paths.
Now that we have our entire root folder in the Docker Context, edit the Dockerfile
that builds Apache to make it able to copy the file test.txt
:
# docker/build/apache/Dockerfile
FROM httpd:2.4
# Copy Symfony files: COPY [FROM_MACHINE] [TO_CONTAINER]
COPY test.txt copied-test.txt
We are using the COPY
instruction.
As the context is our root folder, we simply use the file name test.txt
and copy it to the container as copied-test.txt
.
Now run the command docker-compose build
:
MacBook-Pro-di-Aerendir:app-aragog-www Aerendir$ docker-compose build
php uses an image, skipping
mysql uses an image, skipping
Building apache
Step 1/2 : FROM httpd:2.4
---> fb2f3851a971
Step 2/2 : COPY test.txt copied-test.txt
---> 287d3e3b6a04
Successfully built 287d3e3b6a04
Successfully tagged app-aragog-www_apache:latest
As usual, php
and mysql
are skipped, BUT apache
has now two steps, and the second one is exactly the COPY
instruction.
Let’s see if the file was really copied.
First, recreate the containers running the up
command:
MacBook-Pro-di-Aerendir:app-aragog-www Aerendir$ docker-compose up -d
Recreating app-aragog-www_apache_1 ... done
Starting app-aragog-www_php_1 ... done
Then enter the container (use docker ps
to find the ID of the apache
container) and list the files in it:
MacBook-Pro-di-Aerendir:app-aragog-www Aerendir$ docker exec -it e4ed20c531ae bash
root@e4ed20c531ae:/usr/local/apache2# ls
bin build cgi-bin conf copied-test.txt error htdocs icons include logs modules
BINGO! Our test.txt
file is there with the name copied-test.txt
!
Now that we can use the context
to copy files in the container from it, we are ready to further explore the copying of Symfony files to the container.
Moving Symfony’s files into the container
Now that we know how to move files to the container, we are ready to move our entire app to the container.
We can use a simple instruction like this:
COPY . ./htdocs
This will move the entire context (that is our entire root folder) in the creating container.
WARNING: This is not the best way of doing this. For the moment we make simple things, so it is ok to copy the entire root folder.
Later in this series of posts, we will learn how to refine the files copied in the container to speed up and optimize the process.
For the moment, let’s try this quick and dirty solution.
Edit the docker/build/apache/Dockerfile
, adding the above instruction:
FROM httpd:2.4
# Copy Symfony files
# COPY test.txt copied-test.txt <- Remove this line (and also the text.txt file!)
COPY . ./htdocs
Rebuild the containers:
MacBook-Pro-di-Aerendir:app-aragog-www Aerendir$ docker-compose build
php uses an image, skipping
mysql uses an image, skipping
Building apache
Step 1/2 : FROM httpd:2.4
---> fb2f3851a971
Step 2/2 : COPY . ./htdocs
---> 3f3528537fe9
Successfully built 3f3528537fe9
Successfully tagged app-aragog-www_apache:latest
And update the running ones:
MacBook-Pro-di-Aerendir:app-aragog-www Aerendir$ docker-compose up -d
Recreating app-aragog-www_apache_1 ...
Recreating app-aragog-www_apache_1 ... done
Starting app-aragog-www_php_1 ... done
Now enter the container and check if the files are in it:
MacBook-Pro-di-Aerendir:app-aragog-www Aerendir$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8b10afca67dd app-aragog-www_apache "httpd-foreground" 8 seconds ago Up 4 seconds 8100->80/tcp app-aragog-www_apache_1
c633398a7d65 mysql:5.7 "docker-entrypoint.s…" 15 hours ago Up 15 hours 3306/tcp app-aragog-www_mysql_1
MacBook-Pro-di-Aerendir:app-aragog-www Aerendir$ docker exec -it 8b10afca67dd bash
root@8b10afca67dd:/usr/local/apache2# ls
Docker.md README.md bin build cgi-bin composer.json composer.lock conf config docker docker-compose.yaml docs error htdocs icons include logs modules public src symfony.lock var vendor
Are they there? Very good! We have just moved all the Symfony’s files in the container! 🙂
Let’s try to open our Symfony app: go to http://127.0.0.1:8100
…
The message “It works!”? What’s happening?
Ok, try this: http://127.0.0.1:8100/public
… A list of files? ? And there is our index.php
listed ?
Click on it…
What are you seeing? What, the content of the php
file, not interpreted but instead served as a simple text file? ?
What is damn happening? ?
Making Apache interpret our PHP files
Technically, we have just created our stack and it works because we are able to make up and running our containers with Apache and MySQL (PHP currently doesn’t work, in fact you have never seen it when running docker ps
!).
And it works because we can see the Symfony’s index.php
content.
So, technically it is working, practically it isn’t as we are not able to serve the php files as interpreted webpages.
Why does this happen?
Let’s recap our current configuration:
version: '3.7'
services:
# "php" was "language" in previous example
php:
image: php:7.2
# Configure the database
mysql:
image: mysql:5.7
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-root}
# Configure Apache
apache:
build:
context: .
dockerfile: docker/build/apache/Dockerfile
ports:
- "8100:80"
As you can see we are building three containers: one for php
, one for mysql
and one for apache
.
Leaving apart for the moment the container for mysql
, let’s examine deeper what we are doing with php
and apache
containers.
We already know that the php
container doesn’t work and never starts nor is built: maybe it is time to understand why!
To build php
and apache
we are using two images:
- PHP:
php:7.2
- Apache:
httpd:2.4
(this is in the Dockerfile indocker/build/apache
)
So, the question at this point is: what happens when we build these images?
The first thing we know is that the php
container is skipped. When running docker-compose build
, in fact, we can read a clear message:
php uses an image, skipping
The second thing we know is that the php
container is never run: using docker ps
, in fact, we have never seen it in the list:
MacBook-Pro-di-Aerendir:app-aragog-www Aerendir$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d109c4b4c431 app-aragog-www_apache "httpd-foreground" 11 minutes ago Up 11 minutes 8100->80/tcp app-aragog-www_apache_1
844167c3154f mysql:5.7 "docker-entrypoint.s…" 12 minutes ago Up 12 minutes 3306/tcp app-aragog-www_mysql_1
So, in the end, we have this situation:
- We have the Apache container running, but without PHP;
- We have the PHP container NOT running, and we anyway don’t have PHP (but, as seen in the previous post about how to configure the Apache web server on Docker, if we try to check the version of PHP, we get it, so it seems it is working: very confusing!).
Why?
Let’s see what these images do.
Introducing Docker images (and containers)
A Docker image is nothing more than a Dockerfile with some instructions to build a container.
More precisely
An instance of an image is called a container. You have an image, which is a set of layers as you describe. If you start this image, you have a running container of this image. You can have many running containers of the same image.
You can read more about images and layers here.
The relvant part is this:
A Docker image is built up from a series of layers. Each layer represents an instruction in the image’s Dockerfile. Each layer except the very last one is read-only.
…
Each layer is only a set of differences from the layer before it. The layers are stacked on top of each other. When you create a new container, you add a new writable layer on top of the underlying layers. This layer is often called the “container layer”. All changes made to the running container, such as writing new files, modifying existing files, and deleting files, are written to this thin writable container layer. The diagram below shows a container based on the Ubuntu 15.04 image.
And this is the representation of what you’ve read until now:
But from where these images come?
From the Docker Hub!
So, to understand why our httpd
image doesn’t interpret correctly PHP files and why our php
image doesn’t run in a container, we can read the Dockerfiles of them.
And here they are:
Let’s start examining first the httpd
page.
If your read it, there is a section called “How to use this image.” that states this:
This image only contains Apache httpd with the defaults from upstream. There is no PHP installed, but it should not be hard to extend. On the other hand, if you just want PHP with Apache httpd see the PHP image and look at the
-apache
tags.
It is clear, now?
Our service doesn’t interpret PHP files because using the image httpd
simply we don’t have it!
So, bascially, we are using the wrong image ?.
Let’s use the right one!
Configuring an Apache web server with PHP (for real!)
So, we need to use the the php
image: and this is what we were already using.
But reading the description from the httpd
image, it is not sufficient to use the image php:7.2
like we did: we need to use an image tagged with -apache
!
So, go to the php
image page on Docker Hub.
If you read the page, there is a section called “With Apache” that says:
More commonly, you will probably want to run PHP in conjunction with Apache httpd. Conveniently, there’s a version of the PHP container that’s packaged with the Apache web server.
And the instructions say that we need to use the image php:7.2-apache
: very very well ?
So, what happened was this:
- We saw the version of PHP because effectively we were building a container with it, so Docker was able to find it and returned its version;
- We were not able to serve an interpreted PHP file as we were using the Apache container that is not equipped with PHP.
So, what we need to do now is:
- Change the image we use for PHP in
docker-compose.yaml
; - Use a
Dockerfile
to create thephp
service (to merge the current configuration of the Apache container in the PHP container); - Copy all files from our machine to the new PHP image (instead of copying them in the Apache container);
- Remove the old service with the
httpd
image (as it will be substituted by thephp:7.2-apache
image); - Remove the folder
docker/build/apache
as not needed anymore.
This is the resulting docker-compose.yaml
file:
version: '3.7'
services:
# "php" was "language" in previous example
php:
build:
context: .
dockerfile: docker/build/php/Dockerfile
ports:
- "8100:80"
# Configure the database
mysql:
image: mysql:5.7
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-root}
# Configure Apache <- THIS NODE AS TO BE ENTIRELY REMOVED
# apache:
# build:
# context: .
# dockerfile: docker/build/apache/Dockerfile
# ports:
# - "8100:80"
Concretely, we have renamed apache
to php
and changed the path to the Dockerfile
: nothing really complex.
Now the last thing remained is renaming the folder docker/build/apache
in docker/build/php
.
We also need to change the folder in which we move the files of our app as per documentation:
COPY . /var/www/html/
Where
src/
is the directory containing all your PHP code.
So, in the file docker/build/php/Dockerfile
, we need to change the FROM
instruction to use the image php:7.2-apache
:
# FROM httpd:2.4 <- Change this to use the PHP image
FROM php:7.2-apache
# Copy Symfony files
# COPY . ./htdocs # <- Remove this
COPY . /var/www/html/ # <- Keep attention: we have removed the "." (dot) at the beginning of the destination path
Last thing to do is building the image:
MacBook-Pro-di-Aerendir:app-aragog-www Aerendir$ docker-compose build
Building php
Step 1/2 : FROM php:7.2-apache
7.2-apache: Pulling from library/php
be8881be8156: Already exists
69a25f7e4930: Already exists
65632e89c5f4: Already exists
cd75fa32da8f: Already exists
15bc7736db11: Pull complete
b2c40cef4807: Pull complete
f3507e55e5eb: Pull complete
e6006cdfa16b: Pull complete
a3ed406e3c88: Pull complete
745f1366071d: Pull complete
bdfcada64ad8: Pull complete
86f2b695cc77: Pull complete
5f634a03970a: Pull complete
a329a7ebde19: Pull complete
fb3d2649f534: Pull complete
Digest: sha256:8188b38abe8f3354862845481452cd3b538bc0648e3c5cdef4ef9ee9365fe2d3
Status: Downloaded newer image for php:7.2-apache
---> 5e5a59788e34
Step 2/2 : COPY . /var/www/html/
---> a0cfd25e4828
Successfully built a0cfd25e4828
Successfully tagged app-aragog-www_php:latest
mysql uses an image, skipping
As we have removed a service, we need to run first docker-compose down --remove-orphans
and then docker-compose up -d
.
Once containers are created again, we can go to http://127.0.0.1:8100/public
and we will see the most beautiful page in the world ?:
We are now ready to start building our Symfony based app… or maybe we aren’t?
Mmm, no, we are not still ready ???
What do we need to do now? ?
Conclusions and next steps
Well, also if we are able to copy the Symfony’s files in the web server container (that now is not httpd
anymore, but, instead, php:7.2-apache
) and we are able to make the web server serve interpreted PHP files, we still have some other things to do:
- Understand why we can see Symfony only pointing directly to the
public
folder (SPOILER:DocumentRoot
?); - Checking our web server meets all the Symfony’s requirements;
- Maybe, also refine our copying of files as it is currently very dirty and heavy.
But for the moment we have reached some great goals:
- We know how to create a real Apache web server on Docker;
- We know how to deal with images, to understand how to chose and use them (now, for example, you can use MongoDB if you like: go to search for it!);
- We know how to copy files in the containers and in doing this, we have understood what are
Dockerfile
s (one of the foundation of Docker!), what is the Docker Context, how it works and how we can manipulate it (almost: there is at least one other thing you need to know to master it… Next post!).
Those are a lot of things and we are closer to our goal of fully using Symfony on Docker!
The next post will teach you how to solve the problems I mentioned.
In the meantime, remember to “Make. Ideas. Happen.”.
I wish you flocking users!
Algirdas says
Hi, so far I am enjoying this tutorial. But at the end I got stuck. Maybe you could help?
Everything seemed fine until at the wery end we added “php:7.2-apache” to Dockerfile then running “docker-compose build”, “docker-compose down –remove-orphans” and “docker-compose up -d”. Then we went to “http://127.0.0.1:8100/public”
Over there I got a fiew errors:
ErrorException
Warning: file_put_contents(/var/www/html/var/cache/dev/srcApp_KernelDevDebugContainerDeprecations.log): failed to open stream: Permission denied
AND
RuntimeException
Unable to write in the cache directory (/var/www/html/var/cache/dev)
BTW on the last “docker-compose build” you got a long resposne while mine was only:
$ docker-compose build
mysql uses an image, skipping
Building php
Step 1/2 : FROM php:7.2-apache
7.2-apache: Pulling from library/php
Digest: sha256:249f28517d12ccc89cc5e8ec6f661cab8da6c98f5bfcf1baf4da5874142fd8ad
Status: Downloaded newer image for php:7.2-apache
—> b98367bdaf03
Step 2/2 : COPY . /var/www/html/
—> a34fd5e701f4
Successfully built a34fd5e701f4
Successfully tagged fooder_php:latest
Maybe you know whats wrong?
Algirdas says
Hi, I resolved my problem. I used chmod in Dockerfile to do it. Now I have another question. When are you planning to continue these posts? They are really well written and helpfull
Aerendir says
I hope very soon… They are basically written: I need only the time to revise and then publish them.
Man says
Hi,
When you think you gonna publish the next part of this series?
Aerendir says
Unfortunately, I cannot give you a specific date. I have them already wrote but they are not ready for publishing.
They are on track to be published, anyway, ASAP.
Rashid says
Hi Aerendir,
Thanks for these amazing blog posts. I have followed it and it worked pretty well. I have run into some problems e.g. my application is using APC cache, Intl extension, Curl etc. Also if I try to access the dev environment e.g. /app_dev.php, it says “You are not allowed to access this file. Check app_dev.php for more information” you cannot How can I specify them? It’d also be great if you could publish your new blog post and continue writing them.
Thanks
Aerendir says
Hi Rashid, unfortunately from here I’m not able to know which are tha causes of your issue.
As you can see from this series of posts, the configuration of a Docker container is a trial and error ongoing process.
Did you checked app_dev.php? Maybe also the logs contain other useful information. Follow them and you’ll be able to fix the issue.
About the new blog posts, at the moment I’m focusing on other things but they are on the track to be published soon!
Good luck!
Christian says
Hello Aerendir,
Your tutorial has been a great help to get me familiarized with Docker, i can’t wait for you to publish the next blog posts.
Carry on and many thanks !
Christian
Aerendir says
Hi Christian, very glad to hear this from you! I’ll publish the next posts ASAP!
Juan Zapata says
Thanks a lot for that post! Excelent!
Now I need to access MySQL and if it can be with phpmyadmin will be great.
Waiting for your next post 🙂
Athandiwe says
Thanks a lot for that post! Excelent!
Everything was working fine untill i reached the final step. which is to go to http://127.0.0.1:8100/public
The url can not be found. I get this error
“This site can’t be reached127.0.0.1 refused to connect.”
krachleur says
Hi, one of the greatest post and tutorials I’ve ever read, so simple and precise, but I’m losing patience for the next post, plz launch it ASAP.
Thanx.
Christian G says
Hey there, great tutorial, do you have any idea when you’ll post your next post, I can’t seem to find it anywhere on your page
Aerendir says
I didn’t published the other posts yet nor I know when I will 🙁