Docker e Docker Compose com PHP

Contêineres chegaram para ficar, independente do ambiente em que se trabalha. Seja desenvolvimento, homologação ou produção, muitas pessoas estão usando contêineres pela facilidade de uso, pela manutenibilidade, por ser um processo repetível e bastante configurável, além de uma alta e benéfica volatilidade (podemos criar e destruir contêineres ao nosso bel prazer e de forma fácil!!! 😃).

E o Docker, é hoje, a principal tecnologia de contêineres do mercado. Tecnologias de Contêineres e Docker se confundem, mas existem outras tecnologias, como o Core OS rkt. Mas, neste artigo, vamos falar mesmo do Docker e como usá-lo como ambiente de desenvolvimento de PHP.

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.

  1. 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 contêiner. 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 em MYSQL_DATABASEMYSQL_USER e MYSQL_PASSWORD.
    • volumes – volumes sempre é importante em serviços baseados em contêiner, caso queira persistir dados entre iniciações dos contêineres. Caso contrário, os dados se perdem quando o contêiner é 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 contêiner ondes os dados serão guardados. Então, mapeando o volume data 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 contêiner serão apagados e sobrescritos pelos dados encontrados no host.
  2. 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ço php. 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 contêiner. A marcação é sempre na ordem HOST:CONTEINER. No caso, a porta 80 (http) do host, aponta para aporta 80 do contêiner. O mesmo vale para a porta 443 (https). Então, podemos acessar este serviço pelo endereço http://localhost/ ou https://localhost/. Se não fizéssemos isso, teríamos que saber qual o IP do contêiner 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 contêiner, 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.
  3. 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 (UIDGID e USER), 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.

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.

💡 Curtiu o artigo? Compartilhe-o em suas redes sociais e leve-o para outros colegas de área que podem interessar-se pelo tema!

Show CommentsClose Comments

Deixe seu comentário