Tutorial container wordpress com docker compose e suporte SSL

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. Este tutorial aborda a possibilidade de instalar vários containers do wordpress para diversos sites e domínios distintos.

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 ou nginx).
  • 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 tanto no apache (apache2) quanto no nginx (Nginx Proxy Manager).

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. Caso tenha preferência por utilizar o container do nginx proxy manager, as etapas são todas em ambiente web e mais fáceis.

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.

Opção 1 – Configuração caso utilize o servidor apache instalado fora do container

É 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).

 

Opção 2 – Configuração caso utilize container com Nginx Proxy Manager

Levando em consideração que temos o nginx proxy manager instalado em um container, os passos para criação do proxy reverso e solicitação do certificado SSL são simples. Um exemplo de docker compose para instalação do Nginx Proxy Manager:

services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: nginx_proxy
    ports:
      - '80:80'
      - '81:81'
      - '443:443'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    restart: unless-stopped

  db:
    image: mariadb:latest
    container_name: nginx_proxy_db
    environment:
      MYSQL_ROOT_PASSWORD: 'senha_root_do_banco_mudar'
      MYSQL_DATABASE: 'npm'
      MYSQL_USER: 'npm'
      MYSQL_PASSWORD: 'senha_do_banco_mudar'
    volumes:
      - ./mysql-data:/var/lib/mysql
    restart: unless-stopped

networks:
  default:
    external: true
    name: rede_proxy_reverso

Faça login na interface do NPM (nginx proxy manager).

Nginx Proxy Manager - tela de login

Clique no texto:

Proxy Hosts

Nginx Proxy Manager - ir dashboard proxy hosts

Agora clique no botão:

Add proxy host

Nginx Proxy Manager - adicionar proxy hosts

Em domain names vamos adicionar nosso domínio (neste caso um subdomínio):

dev.viniciuspaes.com

Em scheme, vamos optar pelo acesso com SSL, então vamos escolher:

https

Na opção de foward hostname / IP, devemos escolher o IP do servidor onde o container do wordpress será instalado. Atenção, não confundir e escolher o IP interno do container. Como o container será instalado no mesmo servidor onde o NPM está instalado, podemos utilizar o ip de loopback (localhost):

127.0.0.1

Como definimos que o container que será criado irá responder solicitação https na porta 8081, vamos utilizá-la na opção Foward Port:

8081

Nginx Proxy Manager - Exemplo proxy reverso com ssl

Neste mesmo modal de opção, temos um pequeno menu com as opçoes:

Details | Custom locations | SSL | Advanced

Vamos selecionar a aba:

SSL

Vá na opção de SSL Certificate (vai estar em None por padrão) e escolha a opção:

Request a new SSL Certificate with Let's Encrypt

Tudo pronto e configurado. Basta clicar no botão:

Save

Nginx Proxy Manager - solicitar certificado 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}

  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

  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"

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/

Se utilizou o Nginx Proxy Manager, seu certificado está na pasta que definiu no container, neste exemplo, seria algo como:

~/docker/nginx-proxy/letsencrypt/live/nome_domínio/

Caso tenha utilizado um container somente para o certbot, este também 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

Caso o servidor de proxy reverso esteja configurado no mesmo servidor do container é possível omitir as portas da URL nativamente. Se utilizar o NPM (Nginx Proxy Manager), com uma rede interna compartilhada entre o container wordpress e o NPM, você também pode omitir o endereço IP do servidor e colocar o nome do container diretamente no NPM.

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.