Tutorial container wordpress com docker compose e suporte SSL e Apache

Objetivo

Este tutorial tem o objetivo ensinar um passo a passo de como instalar o wordpress como um container utilizando docker, com suporte a um nome de domínio e acesso por http e https. Por padrão o container do wordpress não vem com suporte SSL, vamos ensinar como realizar modificação no container com Dockerfile para habilitar a porta 443. Vamos abordar a utilização do apache como proxy reverso.

Este tutorial também aborda a possibilidade de instalar vários containers do wordpress para diversos sites e domínios distintos. Desta forma, é interessante ressaltar que o tutorial aborda como iniciar suporte SSL dentro do container, ao invés de apenas  habilitar a diretiva HTTP_X_FORWARDED_PROTO.

Caso queira configurar o wordpress sem realizar modificações com Dockerfile (build) de forma enxuta e objetiva com o Nginx Proxy Manager e a diretiva HTTP_X_FOWARDED_PROTO, utilize este tutorial:

Requisitos

  • Software docker instalado e configurado no sistema operacional.
  • Docker compose instalado e configurado.
  • Conexão com internet para download da imagem do wordpress, mariadb (mysql), phpmyadmin, certificado SSL e proxy reverso (apache).
  • Configuração DNS em seu domínio, direcionando para seu servidor.
    • Caso utilize um domínio local, editar o arquivo /etc/hosts em seu computador e adicioná-lo apropriadamente.
  • Se utilizar um servidor caseiro, garantir que seu roteador está redirecionando as portas 80 e 443 para seu servidor.
  • Garantir que seu firewall no servidor não está bloqueando as portas 80 e 443.

Observação

Como temos desejo de configurar um domínio em nossa instalação do wordpress, é comum utilizarmos um proxy reverso, para gerenciar uma solicitação pela porta 80 ou 443 e encaminhar para a porta correspondente que estará mapeada no container. Vamos abordar o arquivo de configuração de proxy reverso no apache (apache2).

Como instalar imagem do wordpress com docker compose tendo suporte a certificado SSL e nome de domínio

Passo 1: Configuração do Servidor de Páginas para proxy reverso

Precisamos preparar nosso ambiente de hospedagem para gerenciar as solicitações http/https e redirecionar para os containers que queremos criar. Caso já tenha o apache instalado em seu servidor, independente de ser em um container ou não, a etapa principal é configurar corretamente o arquivo de virtualhost do domínio com o proxy reverso.

Neste exemplo, vamos supor que queremos instalar o wordpress para o subdomínio dev.viniciuspaes.com. Mas poderia ser para qualquer domínio ou subdomínio que escolher.

Agora vamos definir quais as portas que este container wordpress irá utilizar. Para esse domínio, vamos utilizar a porta 9080 para acesso http, a porta 9081 para acesso com SSL (https) e porta 8082 para acesso a interface do phpmyadmin.

É necessário 2 arquivos de configuração instalados no apache: um para acesso http e outro para acesso https.

Para acesso http, vamos criar o arquivo dev.viniciuspaes.com.http.conf:

sudo nano /etc/apache2/sites-available/dev.viniciuspaes.com.http.conf

Agora cole o conteúdo abaixo dentro do arquivo. Lembre-se de alterar o nome de domínio e a porta que irá utilizar:

<VirtualHost *:80>
ServerName dev.viniciuspaes.com
ServerAdmin postmaster@viniciuspaes.com

ProxyPass / http://127.0.0.1:9080/
ProxyPassReverse / http://127.0.0.1:9080/
ProxyPreserveHost On
ErrorLog ${APACHE_LOG_DIR}/dev.viniciuspaes.com.error.log
CustomLog ${APACHE_LOG_DIR}/dev.viniciuspaes.com.access.log combined

## Redirecionar tráfego HTTP para HTTPS, habilitar somente após configurar certificado SSL
# RewriteEngine on
# RewriteCond %{SERVER_NAME} =dev.viniciuspaes.com
# RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

No arquivo acima, escolhemos a porta 9080 para receber solicitações http no container que ainda vai ser criado.

Agora é necessário solicitar um certificado SSL do letsencrypt pelo certbot.
Caso seu certbot não esteja em um container basta seguir os passos deste tutorial:

Ao solicitar o certificado as chaves serão salvas no caminho:

  • /etc/letsencrypt/live/dev.viniciuspaes.com/fullchain.pem
  • /etc/letsencrypt/live/dev.viniciuspaes.com/privkey.pem

Um arquivo de configuração para o apache também será criado com o nome:

  • dev.viniciuspaes.com.http-le-ssl.conf

Vamos agora editar este arquivo para adicionar as informações do proxy reverso:

sudo nano /etc/apache2/sites-available/dev.viniciuspaes.com.http-le-ssl.conf

O conteúdo do arquivo deverá ficar como:

<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName dev.viniciuspaes.com

SSLProxyEngine on

<Proxy *>
Allow from all
</Proxy>

ProxyPreserveHost On
ProxyRequests Off

ProxyPass / https://127.0.0.1:9081/
ProxyPassReverse / https://127.0.0.1:9081/

ErrorLog ${APACHE_LOG_DIR}/dev.viniciuspaes.com.error.log
CustomLog ${APACHE_LOG_DIR}/dev.viniciuspaes.com.access.log combined

SSLCertificateFile /etc/letsencrypt/live/dev.viniciuspaes.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/dev.viniciuspaes.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf

</VirtualHost>
</IfModule>

Lembrando que no arquivo acima escolhemos a porta 9081 no container, para receber solicitação https (SSL).


Passo 2: Criação de arquivos para configurar o container do wordpress

Vamos supor o desejo de manter todos os arquivos do containers organizados em uma pasta chamada docker, que pode estar dentro da nossa pasta pessoal. Vamos criar então uma pasta para nosso container do wordpress:

mkdir ~/docker/wordpress

Agora precisamos criar 5 arquivos:

  • Dockerfile – este arquivo vai conter algumas tarefas que precisam ser utilizadas no momento de criação do container
  • .env – este arquivo contém a definição de algumas variáveis importantes no processo de setup dos containers: nome de usuário, senha, nome de domínio, etc.
  • 000-default.conf – é o arquivo de configuração que o servidor apache que vem dentro do container do wordpress deve utilizar para acesso http
  • default-ssl.conf – semelhante ao arquivo 000-default.conf, porém com as configurações de acesso por ssl
  • docker-compose.yml – arquivo de configuração dos containers a serem criados

Vamos caminhar até a pasta que criamos para guardar as informações deste container:

cd ~/docker/wordpress

Para criar o arquivo Dockerfile, usamos o comando:

nano Dockerfile

Dentro do arquivo vamos inserir o conteúdo:

#Definir a imagem do container a ser utilizado como base
FROM wordpress:latest

#Habilitar arquivo php.ini de produção caso existente na imagem
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"

#Aumentar o post_max_size de 8MB para 64MB
RUN sed -i -e "s/post_max_size \= 8M/post_max_size \= 64M/g" "$PHP_INI_DIR/php.ini"

#Aumentar o upload_max_filesize de 2M para 60MB
RUN sed -i -e "s/upload_max_filesize \= 2M/upload_max_filesize \= 60M/g" "$PHP_INI_DIR/php.ini"

#Aumentar o tempo da sessão de 1440s para 86400s
RUN sed -i -e "s/session.gc_maxlifetime \= 1440/session.gc_maxlifetime \= 86400/g" "$PHP_INI_DIR/php.ini"

#Modificar o User ID e o Group ID do usuário www-data de dentro do container para seu usuário fora do container
RUN usermod -u 1000 www-data && groupmod -g 1000 www-data

#Copiar o arquivo defaul-ssl.conf que criamos para dentro do container
ADD default-ssl.conf /etc/apache2/sites-available/default-ssl.conf

Copiar o arquivo 000-default.conf que criamos, para dentro do container
ADD 000-default.conf /etc/apache2/sites-available/000-default.conf

#Habiliar o virtualhost definido em default-ssl.conf
RUN a2ensite default-ssl.conf

#Habilitar o virtualhost definido em 000-default.conf
RUN a2ensite 000-default.conf

#Caso sua imagem não tenha mysqli instalado (php:8.3-apache por exemplo), descomente o comando abaixo
#RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli

# Habilitar módulos rewrite, header, expires e ssl
RUN a2enmod rewrite && a2enmod headers && a2enmod expires && a2enmod ssl

#Adicionar o Servername do seu nome de domínio no arquivo apache2.conf
RUN echo "ServerName dev.viniciuspaes.com" >> /etc/apache2/apache2.conf

Vamos criar o segundo arquivo da lista:

nano .env

Vamos colar o conteúdo abaixo dentro do arquivo:

MYSQL_USER=nome_usuario_banco
MYSQL_PASSWORD=senha_do_banco_mudar
MYSQL_DB=nome_banco_dados
DOMAIN=dev.viniciuspaes.com
WP_PREFIX=wp_
USER=1000
GROUP=1000

Para o terceiro arquivo necessário, vamos criá-lo com o comando:

nano 000-default.conf

Seu conteúdo deve ser equivalente ao código abaixo:

<VirtualHost *:80>
       ServerAdmin webmaster@localhost
       DocumentRoot /var/www/html
       ErrorLog ${APACHE_LOG_DIR}/error.log
       CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

Para o quarto arquivo necessário, vamos criar o arquivo de configuração para acesso https:
nano default-ssl.conf

Se conteúdo deve conter:

<IfModule mod_ssl.c>
        <VirtualHost _default_:443>
                ServerAdmin webmaster@localhost
                DocumentRoot /var/www/html
                Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains;"
                Header append X-FRAME-OPTIONS "SAMEORIGIN"
                Header set X-Content-Type-Options nosniff
                Header set X-XSS-Protection "1; mode=block"
                Header always set Referrer-Policy "no-referrer"

                ErrorLog ${APACHE_LOG_DIR}/error.log
                CustomLog ${APACHE_LOG_DIR}/access.log combined

                SSLEngine on
                #SSLCertificateFile     /etc/apache2/ssl-certs/cert-filename.cert
                #SSLCertificateKeyFile  /etc/apache2/ssl-certs/cert-filenamekey.key

                SSLCertificateFile      /etc/apache2/ssl-certs/server.crt
                SSLCertificateKeyFile  /etc/apache2/ssl-certs/server.key

                <FilesMatch "\.(cgi|shtml|phtml|php)$">
                                SSLOptions +StdEnvVars
                </FilesMatch>

                <Directory /usr/lib/cgi-bin>
                                SSLOptions +StdEnvVars
                </Directory>

                BrowserMatch "MSIE [2-6]" \
                     nokeepalive ssl-unclean-shutdown \
                     downgrade-1.0 force-response-1.0
                
                BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
       </VirtualHost>
</IfModule>

Por fim, vamos criar o arquivo docker-compose.yml:

nano docker-compose.yml

Cole o código abaixo dentro do arquivo:

services:

  db:
    image: mariadb:latest
    container_name: ${DOMAIN}_db
    command: '--default-authentication-plugin=mysql_native_password'
    volumes:
      - ./db_data:/var/lib/mysql
    restart: always
    env_file: .env
    environment:
      - MYSQL_USER=${MYSQL_USER}
      - MYSQL_DATABASE=${MYSQL_DB}
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
      - MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD}
    networks:
      - default

  wordpress:
    container_name: ${DOMAIN}_web
    env_file:
      - .env
    depends_on:
      - db
    build: .
    volumes:
      - ./public_html:/var/www/html/
      - ./logs:/var/log/apache2
      - ./000-default.conf:/etc/apache2/sites-available/000-default.conf
      - ./default-ssl.conf:/etc/apache2/sites-available/default-ssl.conf
      - /etc/localtime:/etc/localtime:ro 
      - /etc/timezone:/etc/timezone:ro
      - /etc/letsencrypt/live/${DOMAIN}/fullchain.pem:/etc/apache2/ssl-certs/server.crt
      - /etc/letsencrypt/live/${DOMAIN}/privkey.pem:/etc/apache2/ssl-certs/server.key
    ports:
      - "9080:80"
      - "9081:443"
    restart: always
    environment:
      - WORDPRESS_DB_HOST=db:3306 #nome do container:porta que proverá recursos de banco de dados, neste caso o container chama db e a porta padrão é a 3306
      - WORDPRESS_DB_USER=${MYSQL_USER}
      - WORDPRESS_DB_PASSWORD=${MYSQL_PASSWORD}
      - WORDPRESS_DB_NAME=${MYSQL_DB}
      - WORDPRESS_TABLE_PREFIX=${WP_PREFIX}
    extra_hosts:
      - "${DOMAIN}:127.0.1.1"
    #user: ${USER}:${GROUP} #linha comentada, mas caso necessário configura UID e GID de permissão dos arquivos de acordo com definido no .env
    networks:
      - rede_proxy_reverso
      - default

  phpmyadmin:
    container_name: ${DOMAIN}-phpmyadmin
    image: linuxserver/phpmyadmin
    env_file: .env
    environment:
      PMA_HOST: db #nome do container que proverá recursos de banco de dados, neste caso o container chama db
      PMA_PORT: 3306
      MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD}
    ports:
      - "9082:80"
    networks:
      - default

  networks:
    default:
      name: rede_${DOMAIN} #rede interna dessa stack, onde o phpmyadmin e o db ficam isolados da rede do proxy reverso, mas conseguem conversar com o wordpress
    rede_proxy_reverso:
      external: true

Passo 3: Confirmar caminho de certificados SSL gerados no Passo 1

Neste passo, é importante verificar qual a estratégia que utilizou para gerar o certificado SSL para seu domínio.

Se possui o certbot instalado no seu servidor, os certificados vão estar na pasta:

/etc/letsencrypt/live/nome_domínio/

Caso tenha utilizado um container somente para o certbot, ele costuma utilizar a pasta:

/etc/letsencrypt/live/nome_domínio/

Então é interessante verificar qual opção utilizou para gerar os certificados. Esta etapa é importante para atualizar o arquivo docker-compose.yml do passo anterior:

    volumes:
      - ./public_html:/var/www/html/
      - /etc/letsencrypt/live/${DOMAIN}/fullchain.pem:/etc/apache2/ssl-certs/server.crt
      - /etc/letsencrypt/live/${DOMAIN}/privkey.pem:/etc/apache2/ssl-certs/server.key

Observação! É possível que seus certificados estejam com o diretório criado com sufixo no nome, do tipo: -0001. Exemplo: /etc/letsencrypt/live/dev.viniciuspaes.com-0001/. Ficar atento a esta possibilidade, para definir o caminho correto para o domínio.

Passo 4: Confirmar criação entradas DNS

Neste exemplo vamos configurar um subdomínio para funcionar em um container com wordpress e com suporte SSL. Desta forma, em nossa registrar, precisamos ter uma entrada que converte um nome de domínio ou subdomínio para o ip do nosso servidor. Após criar a entrada, podemos verificar se a mesma está funcionar com o comando:

dig dev.viniciuspaes.com

Vamos ter como saída algo semelhante a:

; <<>> DiG Ubuntu <<>> dev.viniciuspaes.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30
;; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: 4f06f2012983012830129120398124d58fd5aac75eded21cf (good)
;; QUESTION SECTION:
;dev.viniciuspaes.com. IN A

;; ANSWER SECTION:
dev.viniciuspaes.com. 120 IN A 172.67.148.152

Na saída do comando dig acima, podemos ver que exista uma entrada DNS do tipo A configurada, direcionando o nome do subdomínio para o IP do servidor.

Passo 5: Executar o container

Com os passos anteriores configurados corretamente, podemos iniciar nosso container wordpress com suporte tanto http, quanto SSL. Vamos caminhar novamente para a pasta onde está o arquivo docker compose:

cd ~/docker/wordpress

Vamos executar o container com o comando:

docker compose up -d

Lembrete sobre as portas utilizadas:

  • Acesso website http: porta 9080
  • Acesso website https: porta 9081
  • Acesso interface phpmyadmin: porta 9082

Passo 6: Acessar website wordpress pelo nome de domínio

Com o docker-compose.yml executado, é possível verificar se os containers do wordpress, mariadb (mysql) e phpmyadmin estão funcionando corretamente com o comando:

docker ps

Como saída do comando acima, vamos ter algo semelhante ao texto abaixo:

CONTAINER ID   IMAGE            COMMAND                  CREATED          STATUS          PORTS                                 NAMES
e2yf533e       dev-wordpress    "docker-entrypoint.s…"   22 seconds ago   Up 20 seconds   9080->80/tcp, 9081->443/tcp,          dev.viniciuspaes.com_web
47g5b597       mariadb:latest   "docker-entrypoint.s…"   23 seconds ago   Up 21 seconds   3306/tcp                              dev.viniciuspaes.com_db
224907aa       phpmyadmin       "/init"                  23 seconds ago   Up 21 seconds   443/tcp, 9082->80/tcp, 9082->80/tcp   dev.viniciuspaes.com-phpmyadmin

Se na saída acima, na coluna de STATUS não receber nenhum tipo de erro. Aparentemente os containers estão funcionais.
Abra agora o seu browser de internet e acesse o nome de domínio que atribuiu ao container. No caso desse exemplo, vamos acessar:

https://dev.viniciuspaes.com

Atenção, o container do banco de dados demora alguns segundos a mais para iniciar e ficar funcional, comparado ao container do wordpress. Se acessar rapidamente a url do seu nome de domínio, é comum receber o erro de conexão de banco de dados do wordpress. Só aguardar alguns segundos e recarregar a página.

Conclusão

Existem diversas abordagens para configurar o wodpress em formato de container no docker. O container padrão do wordpress vem com o servidor de páginas apache instalado, porém somente com a porta 80 configurada. Este tutorial abordou uma forma de ativar o virtualhost para ssl no container padrão do wordpress e definiu as configurações necessárias para correto funcionamento.