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