Conteineres chegaram para ficar, independente do ambiente em que se trabalha. Seja desenvolvimento, homologação ou produção, muitas pessoas estão usando conteineres pela facilidade de uso, pela manutenabilidade, por ser um processo repetível e bastante configurável, e uma alta e benéfica volatilidade (podemos criar e destruir conteineres ao nosso bel prazer e de forma fácil!!! :D).
E o Docker, é hoje, a principal tecnologia de conteineres do mercado. Tecnologias de Conteineres e Docker se confundem, mas existem outras tecnologias, como o Core OS rkt. Mas, neste post, vamos falar mesmo do Docker e como usá-lo como ambiente de desenvolvimento de PHP. Afinal, o blog é sobre PHP, mas o que mostrarei aqui podem ser aplicados para outras linguagens.
Não irei falar aqui o que é o Docker e o Docker Compose (tecnologia complementar ao Docker para orquestrar serviços baseados em Docker). De vez em quando, darei uma dica ou lembrarei de algo, mas se quiserem saber mais, leiam estes tutoriais:
E para instalação especificamente, basta seguir os links abaixo:
Com as ferramentas instaladas, basta rodar os comandos abaixo para verificar se estão instaladas e as versões (suas versões podem ser mais novas do que essa):
$ docker -v
Docker version 17.06.1-ce, build 874a737
$ docker-compose -v
docker-compose version 1.15.0, build e12f3b9
Montando infra de desenvolvimento
A ideia do post é mostrar uma possível infra de desenvolvimento básica para PHP com:
- PHP 7.1 (com opções para outras versões, Composer e Git)
- Apache ou Nginx ou Servidor Embutido
- MySQL ou PostgreSQL
Então, vamos direto ao ponto e iniciar pelo arquivo docker-compose.yml
, que define nossos serviços. Iniciarei com PHP 7.1.11, Apache e MySQL.
docker-compose.yml
version: "3"
services:
database:
image: mysql:5.7.20
restart: always
environment:
MYSQL_ROOT_PASSWORD: 12345
MYSQL_DATABASE: usuarios
MYSQL_USER: dbadmin
MYSQL_PASSWORD: dbpassword
volumes:
- "data:/var/lib/mysql"
webserver:
image: webdevops/apache:alpine
depends_on:
- php
ports:
- "80:80"
- "443:443"
volumes:
- ".:/var/www/html"
environment:
WEB_PHP_SOCKET: "php:9000"
WEB_PHP_TIMEOUT: 600
WEB_DOCUMENT_ROOT: "/var/www/html"
php:
image: mlalbuquerque/php:7.1
build:
context: ./dockerfiles
dockerfile: php7.1.dockerfile
args:
- "UID=$UID"
- "GID=$GID"
- "USER=$USER"
volumes:
- ".:/var/www/html"
- "./dockerfiles/config/php.ini:/usr/local/etc/php/php.ini"
- "./dockerfiles/config/xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini"
environment:
PATH: "/root/.composer/vendor/bin:${PATH}"
volumes:
data:
Vamos detalhar os serviços e explicar porque trabalho com eles.
- database - aqui estou trabalhando com o MySQL, mas trabalho hoje muito mais com PostgreSQL (depois mostrarei como trabalhar com outros serviços).
- image - aqui estou usando a imagem oficial do MySQL (
mysql
). Também, sempre prefiro especificar a tag que estou trabalhando, pois assim saberei exatamente a versão do banco. - restart - esta diretiva diz como tratar o serviço caso ele pare abruptamente. No caso, ele sempre irá reiniciar se parar. Se for uma parada explícita (você parar o serviço de propósito), ele não reinicia.
- environment - nesta entrada definimos variáveis de ambiente do serviço. Normalmente, são variáveis que serão usadas internamente pelo conteiner. Dessas acima, a única obrigatória mesmo é a
MYSQL_ROOT_PASSWORD
. Todas as outras são opcionais, mas mesmo assim eu configuro por questões de clareza. Assim, sabemos o nome do banco, o usuário e a senha a serem usadas pela infra, respectivamente definidas emMYSQL_DATABASE
,MYSQL_USER
eMYSQL_PASSWORD
. - volumes - volumes sempre é importante em serviços baseados em conteiner, caso queira persistir dados entre iniciações dos conteineres. Caso contrário, os dados se perdem quando o conteiner é parado ou destruído. No caso, esta única entrada em
volumes
é para definir a persistência dos dados do banco na máquina hospedeira (host). Na documentação da imagem, indica o endereço/var/lib/mysql
como o local no conteiner ondes os dados serão guardados. Então, mapeando o volumedata
para este caminho, os dados serão persistidos no host e tudo a cargo do próprio Docker. OBS: LEMBRE SEMPRE que quando mapeamos volumes, os dados que existem no conteiner serão apagados e sobrescritos pelos dados encontrados no host.
- image - aqui estou usando a imagem oficial do MySQL (
- webserver - serviço de servidor web, neste caso, Apache. Estou usando uma imagem não oficial, pois esta imagem oferece variáveis de ambiente interessantes que facilitam a configuração do Apache.
- image - a imagem é do grupo WebDevOps. Bem legal, bem estruturada e com várias opções. Uso a versão do alpine por ser bem menor (24MB) em comparação com outras baseadas em Debian (~90MB) ou Ubuntu (~110MB).
- depends_on - indica uma dependência deste serviço. Ou seja, o serviço
webserver
depende do serviçophp
. Cria uma ordem para iniciar os serviços. Então, este serviço só inicia depois que o serviço do qual ele depende já tiver iniciado. - ports - define as portas mapeadas entre host e conteiner. A marcação é sempre na ordem
HOST:CONTEINER
. No caso, a porta 80 (http) do host, aponta para aporta 80 do conteiner. O mesmo vale para a porta 443 (https). Então, podemos acessar este serviço pelo endereçohttp://localhost/
ouhttps://localhost/
. Se não fizéssemos isso, teríamos que saber qual o IP do conteiner para poder acessá-lo. - volumes - parte bem importante, pois cria um volume, mapeando a pasta local (onde o arquivo docker-compose.yml se encontra) para a pasta
/var/www/html
do conteiner, que é a pasta configurada como Document Root do Apache (veja a seção abaixo). Com isso, todos os arquivos dentro da pasta (pasta do projeto), podem ser servidos pelo Apache. - environment - aqui temos 3 variáveis que nos ajudam a configurar o Apache. Podemos configurar como acessar o PHP via FPM (
WEB_PHP_SOCKET
), o timeout do PHP (WEB_PHP_TIMEOUT
) e o Document Root do Apache (WEB_DOCUMENT_ROOT
). Existem outras variáveis para poderem usar, se quiserem.
- php - serviço que define o PHP usado no ambiente. Neste caso, crio uma imagem nova, que não existe. E o Docker Compose sabe como tratar isso de forma bem simples. Fiz assim já para embutir extensões que normalmente uso, o Composer (programador PHP não tem como não saber o que é, né?) e o Git (o Composer depende dele e podemos usar pro projeto também).
- image - pode colocar o nome que quiser aqui. Eu coloquei seguindo a regra do Docker: NAMESPACE/CONTEINER:TAG (mlalbuquerque/php:7.1).
- build - aqui estão as diretivas de como construir a imagem (docker build). Como a imagem não existe nem localmente nem no Docker Hub, ele usa essas informações para contruir a imagem:
- context - pasta onde irá procurar pelos arquivos necessários para a build da imagem
- dockerfile - caso o arquivo Dockerfile não tenha este nome, deve ter esta diretiva para dizer qual o nome do arquivo. No caso, o arquivo é php7.1.dockerfile e ele se encontra dentro da pasta
./dockerfiles
. - args - argumentos que são passados no momento da build (diretivas
ARG
dentro do Dockerfile). No caso, foram criadas 3 argumentos (UID
,GID
eUSER
), que usam variáveis de ambiente do host ($UID
,$GID
e$USER
) como valores. Um porém: o Docker não tem acesso às variáveis $UID e $GID, portanto, podemos usar um arquivo.env
(um arquivo estilo INI) para definir valores de variáveis de ambiente que não existem. Uma recurso bem legal do Docker Compose.
Depois, mostrarei os arquivos que usei para montar a imagem (
php7.1.dockerfile
) e o.env
. - volumes - mais uma vez, mapeando volumes para poder usar os arquivos dentro do conteiner. No caso do serviço PHP, coloquei 3 volumes: um com mapeamento de pasta, outros dois para poder configurar o PHP e o XDebug, respectivamente. Assim, o conteiner saberá onde procurar os arquivos PHP, poderemos configurar o PHP e o XDebug para usar com IDEs.
- environment - mais uma definição de variável de ambiente interna ao conteiner. Neste caso, estamos colocando um novo caminho no PATH para que o conteiner reconheça o Composer.
A última linha (volumes
) serve para definir um volumes que podem ser usados por qualquer um dos serviços. Usamos o data
no database
. Não tem nenhuma diretiva, pois usa o padrão do Docker.
Além destes arquivos, temos mais dois.
php7.1.dockerfile
ARG PHP_VERSION=7.1.11-fpm-alpine
ARG XDEBUG_VERSION=2.5.5
FROM php:${PHP_VERSION}
ARG UID=root
ARG GID=root
ARG USER
# Instalando extensões necessárias do PHP
RUN apk add --update --no-cache \
alpine-sdk autoconf curl curl-dev freetds-dev \
libxml2-dev jpeg-dev openldap-dev libmcrypt-dev \
libpng-dev libxslt-dev postgresql-dev \
&& rm /var/cache/apk/*
RUN docker-php-ext-configure ldap --with-ldap=/usr
RUN docker-php-ext-configure xml --with-libxml-dir=/usr
RUN docker-php-ext-configure gd --with-jpeg-dir=/usr/include --with-png-dir=/usr/include
RUN docker-php-ext-install \
bcmath calendar curl dom fileinfo gd hash json ldap mbstring mcrypt \
mysqli pgsql pdo pdo_dblib pdo_mysql pdo_pgsql sockets xml xsl zip
# Instalando o XDebug
RUN pecl install xdebug-${XDEBUG_VERSION}
RUN docker-php-ext-enable xdebug
# Configurando o XDebug
RUN echo "xdebug.remote_enable = 1" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
RUN echo "xdebug.remote_autostart = 1" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
RUN echo "xdebug.connect_back = 1" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
# Instalando o Git (Composer usa para baixar alguns pacotes)
RUN apk add --update --no-cache git && rm /var/cache/apk/*
# Instalando o Composer
RUN php -r "copy('http://getcomposer.org/installer', 'composer-setup.php');"
RUN php composer-setup.php
RUN php -r "unlink('composer-setup.php');"
RUN mv composer.phar /usr/local/bin/composer
# Setando o user:group do conteiner para o user:group da máquina host (ver arquivo .env e docker-compose.yml)
# Assim, o Composer passa a usar o mesmo user:group do usuário do host
# Configura também as pastas para o novo usuário
RUN chown -R ${UID}:${GID} /var/www/html
RUN chown -R ${UID}:${GID} /root/.composer
RUN mkdir -p /.composer && chown -R ${UID}:${GID} /.composer
RUN mkdir -p /.config && chown -R ${UID}:${GID} /.config
VOLUME /var/www/html
VOLUME /root/.composer
VOLUME /.composer
VOLUME /.config
USER ${UID}
.env
# Para uso no Docker
# USER => seu usuário do host
# UID => ID do seu usuário (para descobrir => $ id -u)
# GID => ID do seu grupo (para descobrir => $ id -g)
UID=1000
GID=888
Perceba que o arquivo .env
serve para configurar variáveis de ambiente que não existem ou que o Docker não pode usar. A variável $USER o Docker tem acesso, então não precisa setar. As outras precisam e serão substituídas no docker-compose.yml
. Para ver o efeito, rode o comando abaixo e veja as substituições:
$ docker-compose config
Como estará na seção args
da seção build
do serviço php
, serão usados como valores dos argumentos do Dockerfile php7.1.dockerfile
(diretivas ARG nas linhas 4 a 6). Assim, construirá a imagem usando esses argumentos que estão em .env
.
Neste momento, temos um ambiente com Apache 2.4, PHP 7.1 e MySQL 5.7, já com Composer e Git para ajudar no desenvolvimento do projeto. Agora, tudo no seu lugar, basta rodar o comando abaixo para subir o ambiente:
$ docker-compose up
Caso queira subir apenas um dos serviços (caso o serviço dependa de alguém, levanta os dependentes também):
$ docker-compose up database
$ docker-compose up webserver
$ docker-compose up php
Nos dois casos, ele deixa o log dos serviços no terminal. Se quiser deixar o terminal livre, basta colocar a diretiva -d
depois de up
:
$ docker-compose up -d
Pronto!! Ambiente pronto pra usar.
Alternativas
Podemos trabalhar com PostgreSQL ao invés de MySQL ou mesmo com Nginx ou Servidor Embutido do PHP no lugar do Apache. Abaixo, coloco as alterações que precisam ser feitas para essas trocas.
PostgreSQL no lugar do MySQL
Basta trocas as diretivas do serviço database
pelas diretivas abaixo. Lembrar que o YAML precisa respeitar identação:
database:
image: postgres:9.6-alpine
volumes:
- "data:/var/lib/postgresql/data"
environment:
POSTGRES_PASSWORD: usuarios
POSTGRES_USER: dbadmin
POSTGRES_DB: dbpassword
Nginx no lugar do Apache
Mais uma vez, basta trocar as diretivas de webserver
e mudar apenas uma linha em php
:
webserver:
image: webdevops/nginx:alpine
depends_on:
- php
ports:
- "80:80"
- "443:443"
volumes:
- ".:/app"
environment:
WEB_PHP_SOCKET: "php:9000"
WEB_PHP_TIMEOUT: 600
WEB_DOCUMENT_ROOT: "/app"
php:
volumes:
# mude APENAS o primeiro volume neste serviço
- ".:/app" # no lugar de .:/var/www/html
Servidor PHP embutido no lugar do Apache
Não mudamos o docker-compose.yml
, dessa vez. Agora, precisaríamos modificar apenas o arquivo php7.1.dockerfile
para colocar umas diretivas para que use o o servidor embutido. Lembrando que neste caso, não podemos usar HTTPS no projeto. Mas também não precisamos carregar mais um serviço. Meça suas necessidades! Outra coisa, não precisa apagar o serviço webserver
, mas pode apagar tranquilamente, pois não usaremos mais o Apache (ou Nginx).
# Colocar estas linhas no final do arquivo php7.1.dockerfile
EXPOSE 8080
CMD ["php", "-S", "0.0.0.0:8080", "-t", "/var/www/html"]
No caso acima, agora nosso serviço php
já inicia rodando o servidor embutido, na porta 8080 e apontando a pasta /var/www/html
do conteiner como o Document Root de nosso projeto. Neste caso, precisamos reconstruir nossa imagem. Podemos fazer isso através do Docker:
$ docker build -t mlalbuquerque/php:7.1 -f php7.1.dockerfile .
Lembre de usar o mesmo nome de imagem (mlalbuquerque/php:7.1
) e referenciar o arquivo dockerfile (porque não está usando o nome padrão).
Podemos também usar o Docker Compose para tanto:
$ docker-compose build php
Neste caso, fica mais fácil, bastando apontar o nome do serviço, pois nele consta o nome da imagem e todas as diretivas relativas ao build no arquivo docker-compose.yml
.
No arquivo dockerfile php7.1.dockerfile
, como a diretiva CMD
é quem indica o caminho, e esta diretiva sempre pode ser sobrescrita dentro do docker-compose.yml, caso precise apontar para outra pasta, digamos /app/public
, podemos fazer da seguinte maneira no serviço php
:
# coloque esta diretiva a mais no serviço php do docker-compose.yml
command: ["php", "-S", "0.0.0.0:8080", "-t", "/app/public"]
Pronto!! Mudamos o comando e quando iniciar o serviço php
, ele inicia com o comando php -S 0.0.0.0:8080 -t /app/public
.
Conclusão
Temos agora um ambiente para trabalhar com PHP de forma simples e que podemos configurar como quisermos, com opcionais para trabalhar com outros serviços. Nos prṍximos posts falarei sobre outros tópicos:
Montando um ambiente com PHP, Apache e MySQL (e alternativas)- Docker e Docker Compose com PHP- Como trabalhar com este ambiente - Conteinerizando PHP e outras coisas
- Imagem do PHP já vir com algum framework (Laravel, Symfony e Zend Expressive) - Imagem Docker com PHP + Framework(s)
- Docker Compose para testes automatizados com Selenium
- Configurar IDE para trabalhar com o XDebug do conteiner (Netbeans e Visual Studio Code)
- Uso de shell scripts para automatizar mais ainda as tarefas (PHPUnit, Behat, etc)
- Subir as imagens para o Docker Hub
Até o próximo post!!!