0% found this document useful (0 votes)
119 views17 pages

Docker For Local Web Development, Part 2 - Put Your Images On A Diet

In this post, the author discusses optimizing Docker images to reduce size. Specifically: 1) The initial Docker Compose setup resulted in images totaling 1.5GB in size. 2) Alpine Linux is introduced as a smaller base image alternative to typical distributions like Ubuntu. 3) The Docker Compose file and images are updated to use Alpine variants where available, reducing the total size to 700MB. 4) Alpine is generally good for minimizing size but care must be taken if dependencies are missing from its package repositories. Standard distributions may still be preferable in some cases.

Uploaded by

Shirouit
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
119 views17 pages

Docker For Local Web Development, Part 2 - Put Your Images On A Diet

In this post, the author discusses optimizing Docker images to reduce size. Specifically: 1) The initial Docker Compose setup resulted in images totaling 1.5GB in size. 2) Alpine Linux is introduced as a smaller base image alternative to typical distributions like Ubuntu. 3) The Docker Compose file and images are updated to use Alpine variants where available, reducing the total size to 700MB. 4) Alpine is generally good for minimizing size but care must be taken if dependencies are missing from its package repositories. Standard distributions may still be preferable in some cases.

Uploaded by

Shirouit
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 17

osteel's blog Web development resources

Docker for local web


development, part 2: put
your images on a diet
Last updated: 2022-02-19 :: Published: 2020-03-16 :: [ history ]

Been here before?

Get notified of future posts by email:


Part 1: a basic LEMP stack
Part 2: put your images on a diet you are here
Part 3: a three-tier architecture with frameworks
Part 4: smoothing things out with Bash
Part 5: HTTPS all the things
Part 6: expose a local container to the Internet
Part 7: using a multi-stage build to introduce a worker
Part 8: scheduled tasks
Conclusion: where to go from here

In this post
In this series
In this post
Getting started
"I'm not fat, I'm big boned"
Alpine Linux
When not to use Alpine
Conclusion

Getting started
The assumed starting point of this tutorial is where we left things at
the end of the previous part, corresponding to the part-1 branch of
the repository.

If you prefer, you can also directly checkout part-2, which is the final
result of today's article.

"I'm not fat, I'm big boned"


In part 1 of this series, we went through the steps of creating a
simple but functional LEMP stack running on Docker and
orchestrated by Docker Compose, resulting in four containers
running simultaneously.

These containers are based on images that were downloaded from


Docker Hub, each of these images having a different weight. But
how much space are we talking about?

Let's find out with this simple command, to be run from the root of
our project:

$ docker compose images

It will display a table containing the images used by the application


and some information about them, including their weight:

The total amounts to roughly 1.5 GB, which is not light. Why is
that?

Most Linux distributions come with many services that are expected
to cover common use cases; they offer a large amount of programs,
intended to address a broad audience whose needs may evolve
over time. On the other hand, Docker containers are supposed to
run a single process, meaning what they need to perform their jobs
usually doesn't amount to much, and is unlikely to change over
time.

By using standard Linux distributions, we embark a lot of tools and


services we don't always need, unnecessarily increasing the size of
the images in the process. In turn, this has an impact on
performance, security and, sometimes, the cost of deployment.

Is there anything we can do about it?

Alpine Linux
Alpine is a Linux distribution that takes the opposite approach:
focused on security and with a small footprint, it features the bare
minimum by default and lets you install what you actually need for
your application. The dockerised version of Alpine is as small as 4
MB, and most official Docker images provide a version based on
this distribution.

Before we modify our setup, let's get rid of the current one:

$ docker compose down -v --rmi all --remove-orphans

This command will stop and/or destroy the containers, as well as


remove the volumes and images, allowing us to start afresh.

Replace the content of docker-compose.yml with this one


(changes have been highlighted in bold):

version: '3.8'

# Services
services:

# Nginx Service
nginx:
image: nginx:1.21-alpine
ports:
- 80:80
volumes:
- ./src:/var/www/php
- ./.docker/nginx/conf.d:/etc/nginx/conf.d
- phpmyadmindata:/var/www/phpmyadmin
depends_on:
- php
- phpmyadmin
# PHP Service
php:
build: ./.docker/php
working_dir: /var/www/php
volumes:
- ./src:/var/www/php
depends_on:
mysql:
condition: service_healthy

# MySQL Service
mysql:
image: mysql/mysql-server:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_ROOT_HOST: "%"
MYSQL_DATABASE: demo
volumes:
- ./.docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
- mysqldata:/var/lib/mysql
healthcheck:
test: mysqladmin ping -h 127.0.0.1 -u root --password=$$MYSQL_ROOT_PASSWORD
interval: 5s
retries: 10

# PhpMyAdmin Service
phpmyadmin:
image: phpmyadmin/phpmyadmin:5-fpm-alpine
environment:
PMA_HOST: mysql
volumes:
- phpmyadmindata:/var/www/html
depends_on:
mysql:
condition: service_healthy

# Volumes
volumes:

mysqldata:

phpmyadmindata:

Let's break this down. On the Nginx side, we simply appended


-alpine to the image tag, to pull the Alpine-based version
(remember that the available versions of an image are listed on
Docker Hub). We also mounted a new named volume
phpmyadmindata (declared at the bottom of the file) and used
depends_on to indicate that the phpMyAdmin container should be
started first.

The reason is that Nginx will now be serving phpMyAdmin as well


as our PHP application, where previously the phpMyAdmin image
featured its own HTTP server (Apache). As its name suggests, the
5-fpm-alpine tag is the Alpine-based version of the image,
whose container runs PHP-FPM as a process and expects PHP
files to be handled by an external HTTP server.

Where to find help?

Using an external HTTP server for phpMyAdmin is actually not


documented, and I had to dig up some GitHub issue to put me
on the right track. This is a good example of where to find help
whenever official documentations fall short: browsing GitHub
issues is usually a good place to start, as someone is likely to
have stumbled upon the same problem before. I also
sometimes find it helpful to have a look at the image's
Dockerfile, as it is easier to use an image once we understand
how it is built.

As a result, we need an Nginx configuration for phpMyAdmin. Let's


create a new phpmyadmin.conf file in .docker/nginx/conf.d ,
alongside php.conf :

1 server {
2 listen 80;
3 listen [::]:80;
4 server_name phpmyadmin.test;
5 root /var/www/phpmyadmin;
6 index index.php;
7
8 location ~* \.php$ {
9 fastcgi_pass phpmyadmin:9000;
10 root /var/www/html;
11 include fastcgi_params;
12 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name
13 fastcgi_param SCRIPT_NAME $fastcgi_script_name;
14 }
15 }

Again, a pretty standard server configuration that looks a lot like


php.conf , except that it points to port 9000 of the phpMyAdmin
service.

You also need to update your local hosts file to add the new
domain name (have a quick look here if you've forgotten how to do
that):

127.0.0.1 php.test phpmyadmin.test

Back to docker-compose.yml : similar to our PHP application, the


phpmyadmindata volume ensures the phpMyAdmin files are
available to Nginx, the only difference being that instead of
mounting a local folder of our choice (e.g. src ), we let Docker
Compose pick a local folder to mount both onto the Nginx and
phpMyAdmin containers, effectively making the latter's content
available to the former.

Finally, we also got rid of the mapping of port 8080, since we will
now be using Nginx's port 80 directly.

Next in line is PHP. If you followed the previous part, you already
know that we use a Dockerfile located in .docker/php to
describe and build our image.

Replace its content with this one:

1 FROM php:8.1-fpm-alpine
2
3 RUN docker-php-ext-install pdo_mysql

Just like Nginx, the only difference is we appended -alpine at the


end of the image tag to get the Alpine-based version instead.

That leaves us with the MySQL service, which hasn't changed at


all. The reason is that at the time of writing, there is simply no
available Alpine version for the MySQL image, for reasons laid out
in this GitHub issue.

We are now ready to test out our new setup. Run the now familiar
docker compose up -d again, followed by docker compose
images :

The total size of our images now amounts to around 700 MB, which
is less than half the initial weight.

And, as a bonus, you can now access phpMyAdmin at


phpmyadmin.test, instead of localhost:8080.

When not to use Alpine


As often, however, there is no silver bullet. Alpine is great as long
as what you need is available from the official package repository
(which is well-stocked, to be fair, unlike bathroom hygiene aisles at
the moment), but if something is missing you might be in for some
fun in order to add it manually. Unless you are well versed in
system administration, you probably don't want to go there.

So how to pick the right version of an image? A good approach


would be to start with the most minimal available version and move
up the footprint ladder in case of lack of dependency support only.
Although nothing is ever set in stone and you can always change
the base image later, the more complex a Dockerfile, the more
painful it can get to port it to a different Linux distribution.

This is why I'm introducing Alpine so early on: instead of going for
the first available image without a second thought, consider your
options and identify what seems to be the best compromise – it will
most likely save you some headaches down the line.

Also remember that, beyond sheer size considerations, the smaller


the image, the smaller the potential attack surface.

Conclusion
We now have a better idea of how to pick a base image for our
containers, and we optimised our LEMP stack as a result. This is a
good place to upgrade our setup to a more complex three-tier
architecture and to introduce application frameworks, which we will
cover in the next part.

You can subscribe to email alerts below to make sure you don't
miss it, or you can also follow me on Twitter where I will share my
posts as soon as they are published.

Share Tweet Email


Enjoying the content?

Get notified of future posts by email:


What do you think?
14 Responses

Upvote Funny Love Surprised Angry Sad

25 Comments 1 Login
caused this issue. I need to update the article to reflect this, but in the
meantime you can take a look at this commit and apply the same
changes to your setup. Then do docker-compose down followed by docker-
compose up -d, and it should work.

Thanks for pointing this out – that change was made back in April, it's
probably been broken ever since!
• Reply •
osteel Mod Ernie Pantuso • 2 years ago
1. The aim of part 2 is to use reduced size images, for which Alpine is
great. It also makes sense to use the same HTTP server (in our case,
Nginx) for all HTTP requests, instead of having a second, redundant
HTTP server (Apache) for phpMyAdmin. That being said, there's no
obligation not to if that's easier.

Frankly, I don't use phpMyAdmin myself, but Sequel Ace on macOS. I've
only put it in the series because I know it will be familiar to many.

2. While very similar, MariaDB and MySQL are not the same thing. I'm
trying to stick to a pure LEMP stack in this series as to not confuse
people. This is also why I only use official images whenever possible. I
know a lot of tutorials out there use custom images, but I find that
confusing for newcomers. If you're comfortable using third-party images,
however, by all means do! Basically, what I meant is there is no Alpine
version of the official MySQL image.

3. Have you rebuilt the backend's image before restarting your


containers? docker-compose build backend. Then restart your containers
• Reply •
LEMP
|_ config
|_mysql
| |_my.cnf
|_nginx
| |_conf.d
see more

• Reply •
$connection = new PDO('mysql:host=mysql;dbname=demo;
charset=utf8', 'root', 'root');

Which is actually the tutorial's one.

Full explanation

Here's a connection string, with a couple of placeholders:

$connection = new PDO('[DBMS]:host=[HOST];dbname=demo;


charset=utf8', 'root', 'root');

In your docker-compose.yml file, you've defined services.


Each of these services has a name, which is used to
identify the service on the network Docker Compose
see more

• Reply •
In other words, it looks like you wrote new
PDO('mariadb:host=mariadb... instead of new
PDO('mysql:host=mariadb... in index.php. From what I've
gathered after a quick Google search, the first bit of the
connection string should be mysql, whether the database
engine you use is MySQL or MariaDB.
• Reply •
Jackfusion Jackfusion • 2 years ago
another issue is that phpmyadmin does not load on port
8080 since I exposed 8080:80 in the dokcer-compose file.
• Reply •

You might also like