Otimizando imagens docker com multi stage building
Otimizando imagens docker com multi stage building
Objetivo
Reduzir o tamanho de uma imagem para ser utilizada em um container docker.
Ponto de partida
Nesse artigo anterior nós instalamos o laravel e geramos uma imagem.
Vamos revisitar o Dockerfile dessa imagem
FROM php:7.4-cli
WORKDIR /var/wwww
RUN apt-get update && \
apt-get install libzip-dev -y && \
docker-php-ext-install zip
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
php -r "if (hash_file('sha384', 'composer-setup.php') === 'e21205b207c3ff031906575712edab6f13eb0b361f2085f1f1237b7126d785e826a450292b6cfd1d64d92e6563bbde02') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" && \
php composer-setup.php && \
php -r "unlink('composer-setup.php');"
RUN php composer.phar create-project --prefer-dist laravel/laravel laravel
ENTRYPOINT [ "php", "laravel/artisan", "serve" ]
CMD [ "--host=0.0.0.0" ]
Quando utilizamos uma imagem de container em produção queremos que essa imagem fique com o menor tamanho possível, essa imagem está com 554 MB e queremos reduzir seu tamanho.
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
jorgerabello/custom-laravel latest 2a1abc76bf27 About an hour ago 554MB
Note que a nossa imagem base é a php:7.4-cli, porém podemos utilizar uma imagem base baseada em um linux muio enxuto chamado alpine.
Multi stage building
Multi stage building nada mais é do que uma técnica que separa o build de uma imagem em partes, assim podemos separar algumas operações fazendo co que nossa imagem final seja bastante reduzida.
Sendo assim vamos editar o nosso Dockerfile da seguinte forma
# estágio 1 instalação do laravel
# imagem base php:7.4-cli AS build aqui demos um nome a esse estágio chamando ele de build
FROM php:7.4-cli AS build
# definimos o workdir em /var/wwww
WORKDIR /var/wwww
# fazemos a instalação das dependências
RUN apt-get update && \
apt-get install libzip-dev -y && \
docker-php-ext-install zip
# instalamos o composer
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
php -r "if (hash_file('sha384', 'composer-setup.php') === 'e21205b207c3ff031906575712edab6f13eb0b361f2085f1f1237b7126d785e826a450292b6cfd1d64d92e6563bbde02') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" && \
php composer-setup.php && \
php -r "unlink('composer-setup.php');"
# executamos o laravel
RUN php composer.phar create-project --prefer-dist laravel/laravel laravel
# definimos o entrypoint
ENTRYPOINT [ "php", "laravel/artisan", "serve" ]
# e também demos a opção de quem utilizar essa imagem passar parâmetros para ela como host e porta
CMD [ "--host=0.0.0.0" ]
# estágio 2
# vamos utilizar uma imagem bastante enxuta do php
FROM php:7.4-fpm-alpine
# definimos o diretório de trabalho para /var/www
WORKDIR /var/www
# vamos remover o diretório /var/www/html que é inútil pra nós
RUN rm -rf /var/www/html
# aqui fazemos a cópia de tudo que rolou no estágio de build para o workdir atual
COPY --from=build /var/wwww/laravel .
# estamos dizendo que o usuário www-data e o grupo www-data agora são donos do diretório /var/www
RUN chown -R www-data:www-data /var/www
# cria um link símbolico entre public e html
RUN ln -s public html
# vamos expor a porta 9000
EXPOSE 9000
# e executar o php-fpm que deve ficar ouvindo na porta 9000
CMD [ "php-fpm" ]
Agora vamos fazer um build da nossa imagem e ver qual o tamanho dela
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
jorgerabello/custom-laravel latest 306bfbfb321f 4 seconds ago 141MB
Note que a imagem anterior havia ficado com 554 MB e a nova imagem ficou com apenas 141 MB.
Bônus - executando o nginx como proxy reverso
Aqui a ideia é fazer com que o nginx funciona como um proxy para as requisições que serão realizadas ao container do php, algo mais ou menos assim:
sequenceDiagram
CLIENTE ->> NGINX: 1. Faz uma requisição
NGINX-->>PHP: 2. Encaminha a requisição para o PHP
PHP-->>NGINX: 3. Resposta da requisição
NGINX-->>CLIENTE: 4. Resposta da requisição
Organizando a casa
Para que a estrutura de diretório fique organizada, vamos fazer o seguinte, vamos criar um diretório chamado laravel e mover o Dockerfile para dentro desse diretório
mkdir laravel && mv Dockerfile laravel
Vamos criar um segundo diretório chamado nginx e criar dentro desse diretório um arquivo chamado nginx.conf e um Dockerfile
mkdir nginx && touch nginx/Dockerfile && touch nginx/nginx.conf
Nossa estrutura de diretório deve ficar assim
.
├── laravel
│ └── Dockerfile
└── nginx
├── Dockerfile
└── nginx.conf
Podemos verificar no site do laravel como configurar o nginx como proxy reverso, nessa documentação.
Sendo assim vamos adaptar o nosso arquivo nginx.conf
da seguinte forma:
server {
listen 80;
listen [::]:80;
server_name example.com;
root /var/www/html;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass laravel:9000;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
Agora abra o Dockerfile que está no diretório nginx e edite da seguinte forma:
# usaremos a imagem nginx:1.15.0-alpine como base
FROM nginx:1.15.0-alpine
# aqui estamos removendo o arquivo de configuração padrão do nginx
RUN rm -rf /etc/nginx/conf.d/default.conf
# e estamos copiando o nosso arquivo nginx.conf para o diretório onde ficam os arquivos de configuração do nginx
COPY /nginx/nginx.conf /etc/nginx/conf.d
# para que o nginx funcione precisamos ter um arquivo index.php então vamos criá-lo
RUN mkdir /var/www/html -p && touch /var/www/html/index.php
Build
Vamos buildar a imagem do nginx e do php
docker build -f laravel/Dockerfile -t jorgerabello/custom-laravel:latest .
docker build -f nginx/Dockerfile -t jorgerabello/custom-nginx:latest .
Criando a rede
Para que os containers possam se comunicar vamos criar uma rede para eles
docker network create devnet
Rodando
Por fim vamos executar os containers com as imagens acima
docker run -d --network devnet --name laravel jorgerabello/custom-laravel:latest
docker run -d --network devnet --name nginx -p 8080:80 jorgerabello/custom-nginx:latest
Agora basta acessar http://localhost:8080/ no nvageador ou via curl
Publicando as imagens
Agora que nossas imagens estão estáveis, por fim, vamos publicar elas
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
jorgerabello/custom-nginx latest c5d10cd2585b 8 minutes ago 18MB
jorgerabello/custom-laravel latest 570f005e37fc 8 minutes ago 141MB
docker push jorgerabello/custom-laravel:latest
docker push jorgerabello/custom-nginx:latest