Docker WordPress com MySQL compartilhado

Vantagens desta configuração

  1. MySQL único: economiza recursos, mais fácil de fazer backup
  2. Isolamento: cada WordPress roda em container separado
  3. Escalabilidade: adicionar novos sites é rápido
  4. Segurança: containers não expostos diretamente (apenas localhost)
  5. Manutenção: atualizar WordPress individualmente sem afetar outros

Habilite os módulos necessários no Apache:

a2enmod headers proxy proxy_http rewrite
systemctl restart apache2

1. MySQL compartilhado

Crie /opt/mysql/docker-compose.yml:

services:
  mysql:
    image: mysql:8.0
    container_name: mysql-shared
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root_password_forte_aqui
    volumes:
      - ./data:/var/lib/mysql
    ports:
      - "127.0.0.1:3306:3306"
    networks:
      - shared-network

networks:
  shared-network:
    name: shared-network
    driver: bridge

Inicie o MySQL:

cd /opt/mysql
docker compose up -d

Crie os bancos de dados para cada site:

docker exec -it mysql-shared mysql -uroot -p

Dentro do MySQL:

CREATE DATABASE site1_wp;
CREATE DATABASE site2_wp;
CREATE DATABASE site3_wp;

CREATE USER 'site1_user'@'%' IDENTIFIED BY 'senha_site1';
CREATE USER 'site2_user'@'%' IDENTIFIED BY 'senha_site2';
CREATE USER 'site3_user'@'%' IDENTIFIED BY 'senha_site3';

GRANT ALL PRIVILEGES ON site1_wp.* TO 'site1_user'@'%';
GRANT ALL PRIVILEGES ON site2_wp.* TO 'site2_user'@'%';
GRANT ALL PRIVILEGES ON site3_wp.* TO 'site3_user'@'%';

FLUSH PRIVILEGES;
EXIT;

2. WordPress Site 1

Crie /opt/wordpress/site1/docker-compose.yml:

services:
  wordpress:
    image: wordpress:latest
    container_name: wp-site1
    restart: always
    ports:
      - "127.0.0.1:8081:80"
    environment:
      WORDPRESS_DB_HOST: mysql-shared:3306
      WORDPRESS_DB_NAME: site1_wp
      WORDPRESS_DB_USER: site1_user
      WORDPRESS_DB_PASSWORD: senha_site1
    volumes:
      - ./html:/var/www/html
    networks:
      - shared-network

networks:
  shared-network:
    external: true
cd /opt/wordpress/site1
docker compose up -d

3. WordPress Site 2

Crie /opt/wordpress/site2/docker-compose.yml:

services:
  wordpress:
    image: wordpress:latest
    container_name: wp-site2
    restart: always
    ports:
      - "127.0.0.1:8082:80"
    environment:
      WORDPRESS_DB_HOST: mysql-shared:3306
      WORDPRESS_DB_NAME: site2_wp
      WORDPRESS_DB_USER: site2_user
      WORDPRESS_DB_PASSWORD: senha_site2
    volumes:
      - ./html:/var/www/html
    networks:
      - shared-network

networks:
  shared-network:
    external: true
cd /opt/wordpress/site2
docker compose up -d

4. Apache – VirtualHosts separados

Site 1: /etc/apache2/sites-available/site1.conf

<VirtualHost *:80>
    ServerName site1.com.br
    ServerAlias www.site1.com.br

    ProxyPreserveHost On
    ProxyRequests Off

    ProxyPass        / http://127.0.0.1:8081/
    ProxyPassReverse / http://127.0.0.1:8081/

    RequestHeader set X-Forwarded-Proto "http"
    RequestHeader set X-Forwarded-Host  "%{HTTP_HOST}e"

    ErrorLog  ${APACHE_LOG_DIR}/site1_error.log
    CustomLog ${APACHE_LOG_DIR}/site1_access.log combined
</VirtualHost>

Site 2: /etc/apache2/sites-available/site2.conf

<VirtualHost *:80>
    ServerName site2.com.br
    ServerAlias www.site2.com.br

    ProxyPreserveHost On
    ProxyRequests Off

    ProxyPass        / http://127.0.0.1:8082/
    ProxyPassReverse / http://127.0.0.1:8082/

    RequestHeader set X-Forwarded-Proto "http"
    RequestHeader set X-Forwarded-Host  "%{HTTP_HOST}e"

    ErrorLog  ${APACHE_LOG_DIR}/site2_error.log
    CustomLog ${APACHE_LOG_DIR}/site2_access.log combined
</VirtualHost>

Ative os sites:

a2ensite site1.conf
a2ensite site2.conf
apache2ctl configtest
systemctl reload apache2

5. SSL para múltiplos sites

certbot --apache -d site1.com.br -d www.site1.com.br
certbot --apache -d site2.com.br -d www.site2.com.br

O Certbot ajusta o VirtualHost automaticamente e adiciona o redirect HTTP→HTTPS.

Depois, atualize o RequestHeader para HTTPS:

RequestHeader set X-Forwarded-Proto "https"

6. Corrigir URLs do WordPress atrás de proxy

No wp-config.php (dentro de ./html/) adicione antes de /* That's all */:

define('WP_HOME',    'https://seudominio.com.br');
define('WP_SITEURL', 'https://seudominio.com.br');

// Necessário para que o WordPress reconheça o proxy
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
    $_SERVER['HTTPS'] = 'on';
}

7. Verificação rápida

# Containers rodando?
docker compose ps

# Porta 8080 escutando só no localhost?
ss -tlnp | grep 8080

# Apache do host OK?
systemctl status apache2
curl -I http://seudominio.com.br

Comandos úteis

# Ver todos os sites rodando
docker ps

# Logs de um site específico
docker logs wp-site1

# Parar/iniciar um site
cd /opt/wordpress/site1
docker compose down
docker compose up -d

# Backup do MySQL (todos os bancos)
docker exec mysql-shared mysqldump -uroot -p --all-databases > backup-$(date +%F).sql

A edição dos parâmetros do PHP pode ser feita no arquivo .htaccess. Exemplo:

php_value upload_max_filesize 64M
php_value post_max_size 64M
php_value memory_limit 128M
php_value max_execution_time 300
php_value max_input_time 300

Docker MySQL 5.7 Crash de Memória

Essa imagem específica do MySQL (tag 5.7) pode fechar inesperadamente após a inicialização caso, por exemplo, ocorra uma alteração no tamanho da memória ou swap do host.

Para resolver essa questão, o exemplo abaixo indica o caminho.

docker run -e MYSQL_ROOT_PASSWORD=password \
  --name mysql57 --ulimit nofile=262144:262144 \
  -d -p 3306:3306 -v mysql57-volume:/var/lib/mysql \
  mysql:5.7 --sql-mode=""

Docker Básico com Ubuntu Server

Seguem informações de referência úteis para criar e executar uma imagem docker para um servidor Ubuntu com finalidade de uso no desenvolvimento de software para web.

Primeiro, o básico. Criamos um contêiner a partir de uma imagem. Essa imagem pode ser baixada do Docker Hub.

docker pull ubuntu:bionic

Estou usando a tag bionic para me referir a uma versão específica igual a mesma utilizada no servidor de produção.

Esse comando acima vai baixar a imagem para nossa máquina que já possui o serviço do doker rodando.

Uma vez que a imagem está baixada, vamos criar um contêiner a partir dela e expor as portas que serão utilizadas com o comando abaixo.

docker run --name bionic -p 8089:80 -p 5439:5432 -it -i ubuntu:bionic

Essas portas correspondem aos seguintes serviços respectivamente:

  • apache2 web server
  • postgresql database server

Esse comando (docker run … -it) vai te enviar para uma sessão dentro do contêiner.

É aconselhável executar uma atualização de pacotes antes de qualquer outro comando.

Volumes

Por padrão um contêiner descarta os dados gravados quando terminado. Para persistir os dados usamos os Volumes.

O comando abaixo cria um volume com o nome de bionic-vol.

docker volume create bionic-vol

Para usar esse volume, montamos o mesmo no comando run do contêiner.

docker run \
--name bionic \
-p 8089:80 -p 5439:5432 \
--mount source=bionic-vol,target=/app \
-it -i ubuntu:bionic

Dessa forma, uma pasta /app poderá ser usada para armazenar de forma persistente qualquer arquivo ou estrutura de pastas (ex: local onde o servidor de banco de dados guarda seus arquivos).

Agora vamos aprimorar esse comando para atender a necessidade de um desenvolvedor web com persistência de dados e apontamento da pasta do projeto no host pelo web server do contêiner.

docker run \
--name bionic \
-p 8089:80 -p 5439:5432 \
-v $HOME/Projetos:/var/www/html \
-v $HOME/docker/volumes/postgres:/var/lib/postgresql/data \
-it -i ubuntu:bionic

Comandos

Comandos para utilizar para manutenção do contêiner:

  • docker ps – lista contêineres em execução
  • docker attach bionic – conecta o terminal do container
  • docker volume ls – lista volumes instalados

Documentação oficial em https://docs.docker.com/.