And for this,
we could add a nginx.dockerfile here
in our dockerfiles folder.
And we could grab this nginx:stable-alpine image here
as a base image,
then set the working directory
to our configuration folder here,
so this one without the filename at the end,
just this folder,
and add this as a working directory here
so that thereafter we can copy our local configuration file,
this nginx.conf file in the nginx folder
into that working directory.
We can do that by copying nginx,
which is this folder name that holds the configuration,
/nginx.conf into ., so into that working directory.
And thereafter, I also want to run a command
because my config file here is named nginx.conf,
but it should actually be named default.conf
inside of the container.
That is what nginx expects there.
Now, we could rename it here in our local nginx folder,
or we run a command in the container
which renames that nginx.conf file after it was copied in.
And we can do this with the move command, mv,
which is available on Linux on which this container runs.
And then we can move
and therefore rename
the etc/nginx/conf.d/nginx.conf file
or, since that's already our working directory,
just this nginx.conf file
because we already are executing
this command in this folder,
and move it and therefore rename it to default.conf
in that same working directory.
And this command will simply rename nginx.conf,
which is the file we copied in, to default.conf.
And thereafter, we could switch our working directory
to our www/html and all the copy in our source code
by copying in the src folder,
so this folder here,
into ., so into our working directory.
So everything in the src folder is copied
into our working directory.
That's something we could do here.
And by doing that, by adding this dockerfile,
we ensure that we always copy in a snapshot
of our configuration and of our source code into the image,
and we're not relying on just the bind mount.
The bind mount will still help us thereafter
during development
so that the latest code and the latest configuration
is bound into that container.
But if we should deploy this container,
we'll not have these bind mounts,
and then the snapshots copied into the image
will become important.
So that's a little tweak,
a little addition we could make here.
Now, we don't need to specify an entry point or a command
in this dockerfile here
because the nginx image already has a default command.
And if we don't specify our own one,
the default one will be executed.
And that will be the one to start this web server,
which is exactly what I want.
So now we just need to go to docker-compose.yaml
and tweak our code up here.
Instead of using just that base image,
we now wanna use our own image,
and hence, we need the build configuration.
And now here's something which is also very important
because we'll now use some code here
or a certain configuration which we haven't used before.
Previously, we always had something
like context: ., dockerfile:,
and then the name of the dockerfile.
Or to be precise,
in our build configurations in this file,
our context was the dockerfiles folder
since that holds the dockerfiles,
and then we specify the name here.
For example, nginx.dockerfile.
Now, that won't work here
because the context does more than just set the folder
in which this dockerfile can be found.
It also sets the folder
in which the dockerfile will be built.
And that matters.
In this dockerfile, I'm referring to the nginx folder,
and I'm referring to the src folder
in my host machine project folder.
And both folders, nginx and src,
are outside of the dockerfiles folder.
So if I set dockerfiles as my context
and the dockerfile is built in that folder,
nginx and src are unreachable,
and hence, building this image would fail.
So what we actually need to do here
is we need to set the context to .,
which means the same folder as docker-compose.yaml is in,
which is our entire project folder,
and then add the path to the dockerfile here
on the dockerfile instruction.
So now that's dockerfiles/nginx.dockerfile.
By doing that, we ensure that more context,
more folders, are available
because the dockerfile is built in the main project folder
and not inside of the dockerfiles folder.
For the other images, like the php image, this didn't matter
because in the php image,
we're not copying anything from our host machine,
and therefore they're using this context was fine.
But if you need something else from your project folder,
setting the context to some child folder will not work.
So that's important.
Now, we could already try that, but before we do that,
there's also something else I wanna do.
For one, I'll comment out these volumes
so that we try running this without our bind mounts,
which means that source code changes won't be reflected,
but we should still see our starting page,
changes won't be reflected though,
but the starting page should be there
since we copy a snapshot into our container.
But besides that,
I also wanna tweak that php.dockerfile here
because here it's very similar.
I'm also setting this bind mount,
which is great during development,
but which won't help us if we ever should deploy this
because then this php.dockerfile
actually will have no code inside of it.
And that, of course, means that no code would be part
of the deploy php container.
So what we wanna do here
is we also wanna copy the src folder into .,
so into this working directory, /var/www/html,
so that our source code, a snapshot of it,
is also available here.
And that means that we now also should be able
to run this php container without this bind mount.
Again, the disadvantage without the bind mount
is that changes to our source code are not reflected,
and we would need to rebuild the image,
but this is just a demo.
We'll comment it back in for development later.
I just want to show you how we could not also run it
such that it would be deployment-ready, so to say.
Now, since my php.dockerfile now does refer
to a folder in the project folder,
we also need to adjust our context here now
and instead adjust the dockerfile to include the path
so that the build context is more than just a child folder.
Now, we don't need to change anything
about the other services
because composer, artisan, and npm
are essentially just meant to be executed locally here,
at least at the moment,
so we don't need to change anything here.
And mysql has no bind volume.
This image and container should work just fine.
So if we now save this
and we run docker-composer up -d --build
to force a re-evaluation of all images, of all dockerfiles,
and then we run the server service,
and I type docker-compose instead of docker-composer,
this is now building the php image again
because we're copying in the source code now.
So let's now wait for all of that to finish.
And this is now looking good.
If I now reload localhost:8000, I get an error though.
Permission denied.
And that error is related to the php image
which we're using in a php.dockerfile.
We're also copying our source code
into this working directory here.
And the problem we have is that once the container
is generally able to read and write,
this image actually restricts
read and write access by the container.
This wasn't a problem with the bind mount
which we used before,
but it is a problem if we work only inside of the container
as we're doing it now.
The solution is to give our container write access
to certain folders to be precise to our source code folder,
our working directory here.
And we can do this by running a number of command
instead of the image when it's being created,
and that's the chown command,
which is a Linux command for changing folder ownership
and controlling who's allowed to read or write to folders.
And we wanna add the -R flag here,
which in the end is just as needed
to change the ownership of an entire folder
by doing it recursively
for all folders and files inside of it.
And now here we need
to give a special user more permissions,
and that special user is the www-data user,
which is the default user created by this php image.
And that's the default user who, by default,
has no write access to this working directory.
So now here we need to add a colon
and then repeat that username in the end
to control that this user has read and write access,
and then specify the folder name,
in this case /var/www/html.
And this might look strange,
especially if you have no experience with Linux commands.
It simply ensures that this default user,
which is set up by this php image,
has write access to this folder which holds our source code.
And since Laravel needs to generate files in that folder
during the php code execution,
for example, log files or cache views,
we need to grant this write access here.
With this change made, we can run docker-compose down
to bring down all running services.
And then we can again, of course, run
docker-compose up with the --build flag
to ensure that this new dockerfile is taken into account
and this command is being executed
and the image is being rebuilt.
And once that is done
and once everything is up and running again,
we should then be able to visit
this running Laravel application.
Now, this is definitely advanced,
and it's all pretty specific to this exact application
and to Laravel needing write access here.
It is something you can all find via Google though
if you, for example, googled this part
of the error message which we got.
If I now reload, we see the starting page again,
and now it's all served just with code
and configuration snapshots
which are copied into our images.
And, again, we don't need this during development,
but if you want an environment
which you cannot just use during development
but which is also having at least some required preparation
for production, for deployment,
then these are changes you wanna make.
With that though, I'll bring back the bind mounts
so that we still can have live code changes being reflected
in our running application.
But now we simply have an application
that's prepared for both,
snapshots being copied into the images when they're built,
and then bind mounts to always have access
to the latest code from inside the container.
Now, one tiny change, by the way,
here on the artisan command,
this all uses the php.dockerfile,
and our extra additions made there are no problem for that.
But we wanna ensure that we now also set the context
to the entire project folder
and change the dockerfile to dockerfiles/php.dockerfile.
Otherwise, running this command would fail.
With that, however, we should be able to run
docker-compose run --rm artisan migrate,
and it should work just fine as before.
Yes, it does.
So now that's the finished setup
with which I wanna end this section now.
Definitely a lot of content covered here,
also a couple of new concepts
or additions to what you learned before,
and definitely all the setup which can be difficult to grasp
if you have no Laravel application
and if you are new to Docker.
My primary goal here
was to show you such a more complex setup
and show you a couple of pain points
you might be facing here.
So if you're thinking,
"Wow, that's something I would have never built on my own,"
that is totally fine here.
We're not done with the course at all.
And especially if you have no Laravel knowledge,
knowing about these building blocks
and how they work together is extra hard.
I still hope you got a lot out of this module
and learned more about Docker and Docker Compose,
how the different parts work together there,
and when you use what.
We'll have more examples throughout this course.
But in the next module,
we'll actually focus on deploying containers.