DevOps

Docker e Docker Composer na Prática – Criando um Ambiente Laravel

Por em

Durante muito tempo, tarefas como configurar um ótimo ambiente de desenvolvimento foram consideradas um grande desafio para desenvolvedores de todos os níveis. Softwares, compiladores, IDEs, banco de dados e principalmente: A compatibilidade com os ambientes de produção.

Nesse guia rápido trataremos essencial do Docker, uma ferramenta que definitivamente mudou completamente a forma que todos nós, desenvolvedores, trabalhamos. Através de isolamento de namespaces, utilização de cgroups e um sistema de arquivos baseado em camadas, o Docker possibilita que subamos em questão de segundos um ambiente completo e idêntico ao de produção.

Vamos então colocar a mão na massa.

O que é Docker?

Docker é uma plataforma Open Source que possibilita o empacotamento de uma aplicação dentro de um container. Uma aplicação consegue se adequar e rodar em qualquer máquina que tenha essa tecnologia instalada.

De acordo com o www.docker.com, um container é uma unidade padrão de software que empacota o código e todas as suas dependências para que o aplicativo seja executado de maneira rápida e confiável de um ambiente de computação para outro. Uma imagem de container do Docker é um pacote de software leve, autônomo e executável que inclui tudo o que é necessário para executar um aplicativo: código, tempo de execução, ferramentas do sistema, bibliotecas do sistema e configurações.

Containers

Disponível para aplicativos baseados em Linux e Windows, o software conteinerizado sempre será executado da mesma maneira, independentemente da infraestrutura. Os containeres isolam o software de seu ambiente e garantem que ele funcione de maneira uniforme, apesar das diferenças, por exemplo, entre desenvolvimento e preparação.

O Docker, especificamente, foi feito para ser trabalhado com o Linux, porém, é totalmente possível trabalharmos com o Docker no MAC e no Windows, já que o MAC possui um sistema de virtualização próprio e o Windows, pelo menos na versão Professional, possui o Hyper V.

No Windows, temos duas opções para rodar o Docker: se você possui o Windows na versão Professional, basta ativar o Hyper V, em outras versões, porém, essa opção de ativação do Hyper V não está disponível, dessa forma, portanto, deveremos utilizar Docker Toolbox. Resumidamente, o Docker Toolbox sobe uma máquina virtual em seu sistema, instala uma versão do Linux e o seu Docker irá “conversar” com essa máquina virtual.

E agora, preparados para a prática?

Não iremos abordar a instalação do Docker, pois podemos partir do pressuposto de que somos todos desenvolvedores e que nós todos temos a capacidade de instalar o Docker.

Vamos resolver um problema pontual de muitos desenvolvedores: Configuraremos um ambiente de desenvolvimento utilizando o Docker e o Docker Compose. Para isso, utilizaremos como exemplo o setup de uma aplicação baseada no framework PHP Laravel.

Mãos à obra: Projeto Laravel

Para iniciar o processo, criaremos um simples projeto Laravel. Esse projeto será chamado de laraveldocker. Para isso, faça o download do “composer” (gerenciador de pacotes do PHP. Para isso acesse: getcomposer.org).

Em seu terminal digite o comando:

composer create-project --preffer-dist laravel/laravel laravel-docker

Composer

O Laravel será instalado em sua máquina e em seguida será criado o projeto laravel-docker. ( esse processo poderá levar alguns minutos.

Após a instalação, acessaremos o projeto:

cd laravel-docker
ls

cd laravel-docker

Note que o projeto já está rodando em nossa máquina. Se dermos o comando à seguir, poderemos comprovar isso:

php artisan serve

artisan serve

Acessando o localmente de nosso navegador, poderemos ver a aplicação rodando:

Laravel

Por que utilizar o Docker?

Nesse momento você deve estar se perguntando: “Mas é tão fácil criar um projeto Laravel, localmente, pra que eu preciso do Docker”

A resposta é bem simples: Para garantir que outros desenvolvedores, que poderão trabalhar em seu projeto, de máquinas diferentes, possam trabalhar exatamente no mesmo ambiente que você, independente da máquina utilizada, todos terão os mesmo recursos, por exemplo, mesma versão do PHP, MySQL, recursos computacionais e etc…

Com isso, aquela história de “Na minha máquina funciona”, com certeza vai acabar.

Primeiros passos com Docker

Agora que já temos nosso projeto instalado, vamos checar rapidamente se o Docker já está disponível em nosso ambiente, caso não, faça a instalação em: www.docker.com.

Digite o comando docker em seu terminal. Você poderá notar que uma lista de outros comandos aparecerão. Isso significa que ele está corretamente instalado:

$ docker

Docker
Docker
Docker

Fixando alguns conceitos básicos do Docker: Containeres e Imagens.


Container: é o local onde a sua aplicação ficará rodando.


Imagem: É como um snapshot. Outros desenvolvedores com acesso a esta imagem, terão os mesmos recursos que você utiliza e configurou em seu container.

Para que você possa trabalhar com o Docker, é extremamente necessário que você conheça seus principais comandos. Vamos lá!

O primeiro comando é o docker ps , esse comando irá lhe mostrar quais o conteineres foram criados e estão rodando:

docker ps

Um segundo comando, muito importante, é o docker images , esse comando mostra quais imagens foram criadas:

docker images

Note que ainda não criamos nenhum container e, consequentemente, nenhuma imagem, por isso nenhuma informação é apresentada.

Vamos abrir esse projeto utilizando nossa IDE. Neste artigo iremos trabalhar com o Visual Studio Code.

Visual Studio Code

Dockerfile e Docker Compose

Dockerfile

Agora, iremos criar um arquivo chamado Dockerfile.

“O Dockerfile é um arquivo de texto que contém as instruções necessárias para criar uma nova imagem de contêiner. Essas instruções incluem a identificação de uma imagem existente a ser usada como uma base, comandos a serem executados durante o processo de criação da imagem e um comando que será executado quando novas instâncias da imagem de contêiner forem implantadas.” (Fonte: www.docker.com)

Vamos então criar um arquivo chamado Dockerfile.

Dockerfile

Por padrão, utilizaremos uma imagem Docker que nos trará o PHP-FPM e o Nginx já configurados. Você poderá encontrar essa imagem acessando https://hub.docker.com e pesquisando por nginx php fpm:

docker hub nginx

Em nossa IDE, no arquivo Dockerfile que criamos, inserimos o seguinte código:

FROM wyveo/nginx-php-fpm:latest

A instrução FROM define a imagem de container que será usada durante o processo de criação de nova imagem, wyveo/nginx-php-fpm é o endereço da imagem e latest indica que queremos a versão mais atual dessa imagem.

Dockerfile FROM

É necessário explicar que esse código traz uma instalação crua do nginx com PHP-FPM. Por padrão, o nginx, quando instalado dessa forma, mantém seu Document Root no seguinte caminho:

/usr/share/nginx/html

Docker Compose

O docker-compose é uma ferramenta do Docker que, a partir de diversas especificações, permite subir diversos containeres e relacioná-los através de redes internas.

Para isso, vamos iniciar criando um arquivo chamado de docker-compose.yaml.

docker-compose

No arquivo docker-compose, declaramos a seguinte estrutura:

Estrutura do docker compose

version : declara a versão do docker compose
services: declara quais serviços serão rodados, nesse caso, chamaremos de laravel-app.
build: declara o nome da imagem, ou, no caso, se declararmos o ., ele irá “chamar” a imagem declarada no Dockerfile.
ports: realiza a liberação das portas. Na minha máquina eu quero que libere a porta 8080, porém, lá no meu container eu quero que seja liberada a porta 80, ou seja, toda vez que eu acessar o meu localhost com a porta 8080 o Docker irá redirecionar para a porta 80 o nginx criado no container.

Acessando o nosso terminal, subiremos o container com o comando:

$ docker-compose up -d

docker-compose up -d

Note que após o comando aparecerá as seguintes informações:

docker-compose building

O docker-compose up irá rodar o docker compose, baseado em nosso docker-compose.yaml e com o -d o container é inicializado em segundo plano e podemos utilizar o nosso terminal para outros comandos.

Acessamos o nosso browser e já podemos ver o nosso nginx rodando:

Nginx

Lembrando que, em nosso docker-compose.yaml, indicamos que acessaremos a porta 8080 de nossa máquina e essa acessará a porta 80 do nosso container.

Nosso container rodando:

docker ps

Volumes

O Docker possui um mecanismo de gerenciamento de volumes que com ele é possível compartilharmos um volume da nossa máquina com o container.

Em nosso docker-compose.yaml iremos declarar

volumes:
  - ./:/usr/share/nginx

Quando criamos esse volume, tudo o que estiver em nossa pasta será montado dentro do nosso container, ou seja, tudo o que modificarmos em nossa pasta será compartilhado com o container, porém, caso “matarmos” o container, ainda teremos os arquivos em nossa máquina.

Pastas do projeto ls
Arquivos em nossa pasta

E assim fica o nosso docker-compose.yaml:

docker-compose

Vamos testar?

Com o comando docker-compose up -d --build, subiremos as modificações realizadas em nosso docker-compose.yaml:

docker-compose up -d

Para evitar que tenhamos um erro 404 no nginx (pois ele está buscando por padrão a pasta html), criaremos um link simbólico apontando a pasta public de nosso projeto Laravel para html.

404 not found

Esse link simbólico é criado rodando um ln -s public html em seu terminal. O link simbólico, na verdade, funciona como um atalho, pois toda vez que acessarem a página html na verdade estarão acessando a pasta public:

link simbólico

Note a pasta html:

pasta html

Agora, finalmente podemos visualizar a imagem do Laravel sendo executada.

Laravel

Conectando o Banco de Dados

Agora que temos o nosso Laravel rodando, iremos realizar algumas declarações em nosso docker-compose.yaml.
Criaremos um serviço chamado:

mysql-app:

Nesse serviço vamos declarar que utilizaremos uma imagem MySQL. Essa imagem pode ser facilmente encontrada no https://hub.docker.com.

image: mysql:5.7.22

O nosso MySQL também utilizará portas:

ports:
  - "3306:3306"

Com essa declaração estamos dizendo que, tanto a porta de nossa máquina quanto a porta de nosso container serão as mesmas.

A imagem do MySQL foi preparada para que possamos trabalhar com variáveis de ambiente. Utilizaremos uma variável de ambiente que cria o banco de dados com uma senha, facilitando todo o trabalho.

environment:
  MYSQL_DATABASE: laravel
  MYSQL_ROOT_PASSWORD: laravel

Dando prosseguimento, para que essas máquinas possam conversar entre si, é necessário que compartilhem uma rede interna:

networks:
  -app-network

Esse serviço deve ser criado em nosso laravel-app e em nosso mysql-app.

Declaramos, também, fora dos serviços a criação da rede propriamente dita:

networks:
  app-network:
    driver: bridge

Esse é o resultado final.

docker-compose

Prontos para testar?


Primeiro, vamos derrubar nosso container:

docker-compose down

Em seguida, subimos novamente:

docker-compose up -d

Agora, para que o Laravel possa se conectar com o container do MySQL, vamos rapidamente editar o arquivo .env de nosso projeto. Ele é responsável por todas as configurações desse framework. Nesse caso, vamos alterar as credenciais de acesso ao banco de dados de acordo com o que foi informado no docker-compose.yaml:

Alterando o .env

Iremos alterar o nome de nossa conexão:
DB_HOST=127.0.0.1, deve ter o mesmo nome de nossa conexão, ou seja:
DB_HOST=mysql-app

Alteramos, também:
DB_DATABASE=homestead para DB_DATABASE=laravel e
DB_USERNAME=homestead para DB_USERNAME=root e
DB_PASSWORD=secret para DB_PASSWORD=laravel

.env alterado

Vamos testar a conexão:

docker exec

Agora, deveremos criar o volume no mysql-app, para que o nosso banco não seja perdido caso “matarmos” o nosso container:

Criaremos uma pasta oculta chamada .docker quando declararmos o nosso volume:

volumes:
  - .docker/dbdata:/var/lib/mysql

Na prática, ficará assim em nossa IDE:

docker-compose

Se pararmos o nosso container e depois subirmos novamente, poderá verificar que um novo aquivo foi adicionado em nossa IDE:

docker-compose dow / up

Novo arquivo adicionado:

docker-compose

Se dermos novamente um php artisan migrate, aparacerá a informação de que não temos novos arquivos, pois já estarão salvos em nossa máquina.

artisan migrate

É importante explicar que, caso esteja utilizando o Windows, recomenda-se uma declaração específica em nosso docker-compose.yaml:

command innodb

Sem essa declaração, para usuários do Windows, provavelmente o volume do nosso mysql-app não será montado da forma correta.

Enfim, nossa aplicação Laravel já está pronta, totalmente disponível para desenvolvermos os nossos projetos.

Declarações Adicionais Dockerfile

WORKDIR

“A instrução WORKDIR define um diretório de trabalho para outras instruções Dockerfile, como RUN, CMD e também o diretório de trabalho para executar instâncias da imagem do container.” (Fonte: www.docker.com).

Em nosso Dockerfile, ficará assim:

FROM wyveo/nginx-php-fpm:latest
WORKDIR /usr/share/nginx/

RUN

“A instrução RUN especifica os comandos a serem executados e capturados na nova imagem de contêiner. Esses comandos podem incluir itens como a instalação de software, a criação de arquivos e diretórios e a criação da configuração do ambiente.” (Fonte: www.docker.com)

Como exemplo, em nossa aplicação:

FROM wyveo/nginx-php-fpm:latest
WORKDIR /usr/share/nginx/
RUN rm -rf /usr/share/ngix/html
RUN ln -s public html

A declaração RUN rm -rf /usr/share/ngix/html elimina a pasta html

A declaração RUN ln -s public html cria automaticamente o link simbólico.

COPY

“A COPY é uma instrução que copia os arquivos e diretórios para o sistema de arquivos do container. Os arquivos e diretórios devem estar em um caminho relativo ao Dockerfile.” (Fonte: www.docker.com).

Iremos abordar essa declaração de forma detalhada, em nosso projeto, quando realizarmos o build da imagem para o nosso Docker Hub.

ADD

“A instrução ADD é como a instrução de cópia, mas com ainda mais recursos. Além de copiar arquivos do host para a imagem de contêiner, a instrução ADD também pode copiar arquivos de um local remoto com uma especificação de URL.” (Fonte: www.docker.com).

CMD

“A instrução CMD, define o comando padrão a ser executado durante a implantação de uma instância da imagem do contêiner. Por exemplo, se o contêiner estiver hospedando um servidor Web NGINX, o CMD pode incluir instruções para iniciar o servidor Web com um comando como nginx.exe. Se várias instruções CMD forem especificadas em um Dockerfile, somente a última será avaliada.” (Fonte: www.docker.com).

Em nosso projeto, o resultado será:

Dockerfile

Build da imagem no Docker Hub

Para finalizar esse nosso guia rápido, iremos realizaremos o build de nossa imagem no Docker Hub, dessa forma, outros desenvolvedores poderão utilizar a mesma imagem que criamos.

Nessa etapa, iremos declarar o COPY:

COPY . /usr/share/nginx

Em seguida utilizaremos, novamente o RUN:

RUN chmod -R 775 /usr/share/nginx/storage/*

Em nossa IDE ficara dessa maneira:

Dockerfile

Em nosso terminal, rodaremos o seguinte comando:

docker build -t rafatrevisani/laravel-image:latest .

Sendo seulogin/laravel-image: latest . o nome de nossa imagem.

O comando digitado fará com que seja realizado o build com todas as alterações que fizemos em nosso Dockerfile e, feito o build, poderemos publicar em nosso Docker Hub.

docker build

Após o build, faremos o login em nosso Docker Hub:

docker login

Feito o login, faremos o push da imagem com o comando docker push + nome da nossa tag:

docker push seulogin/laravel-image:latest

docker push

Acessando o nosso Docker Hub, poderemos ver a nossa imagem:

docker hub

Agora a nossa imagem está disponível a outros desenvolvedores e dessa forma, os mesmos poderão trabalhar exatamente com a essa imagem.

Conclusão

Como todos pudemos verificar, trabalhar com o Docker e o Docker Compose não foi nenhum bicho de sete cabeças.

Lembre-se que esse processo não serve somente para o Laravel, mas sim para qualquer linguagem e framework de sua escolha.

Que tal trabalhar em sua própria imagem agora?