← Blog

Hospede o Ghost com MySQL

Railyard

27 de abril de 2026 · 8 min de leitura

Siga isso no seu próprio servidor. Comece grátis, sem cartão

O instalador de uma linha do próprio Ghost te dá um blog funcionando rápido, mas assume, sem avisar, SQLite e uma única máquina que você nunca vai trocar. No momento em que você quiser MySQL, um segundo servidor para jobs em segundo plano, ou simplesmente um arquivo Docker Compose que você possa ler e fazer backup sozinho, você está por conta própria. Este guia monta essa estrutura do início ao fim: Ghost e MySQL como dois containers, Caddy na frente para o HTTPS, backups de verdade e um caminho de upgrade que não depende de cruzar os dedos. No final, você vai ter uma publicação rodando numa infraestrutura que você entende, além da versão de um clique se preferir pular o YAML.

Este guia usa a linha de release estável atual do Ghost (Ghost 6.x) para a configuração manual abaixo, e MySQL 8.0.

O que você vai precisar

  • Um servidor. Um Droplet Basic da DigitalOcean com 2 vCPUs e 2 GB de RAM roda Ghost e MySQL com folga por US$18/mês; o droplet de 1 vCPU / 1 GB por US$6/mês fica apertado demais com MySQL e Node residentes ao mesmo tempo. Qualquer máquina Ubuntu 22.04 ou 24.04 funciona do mesmo jeito.
  • Um domínio, com o registro A do DNS apontado para o IP do servidor antes de chegar na etapa de HTTPS.
  • Um provedor de SMTP (Postmark, SES, Mailgun ou similar) para e-mail transacional. O Ghost precisa disso para convites de equipe, redefinição de senha e links de login de membros; isso não tem relação com o envio de newsletter, que o Ghost cuida por conta própria depois que o e-mail estiver configurado.

Etapa 1: Aponte o DNS e prepare o servidor

Crie o registro A do seu domínio primeiro, já que a emissão do certificado mais adiante depende dele já estar resolvendo:

dig +short blog.example.com

Você deve ver o endereço IP do seu servidor assim que a propagação terminar, normalmente em minutos na maioria dos registradores.

Conecte via SSH e instale o Docker Engine e o plugin Compose:

curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER

Saia e entre de novo para a mudança de grupo ter efeito, depois confirme:

docker compose version

Você deve ver uma string de versão do Compose (v2.x), não um erro de “command not found”.

Etapa 2: Escreva o arquivo Docker Compose

Crie um diretório para o projeto e um docker-compose.yml com três serviços: Ghost, MySQL e Caddy como proxy reverso.

services:
  ghost:
    image: ghost:6-alpine
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      NODE_ENV: production
      url: https://${DOMAIN:?set DOMAIN in .env}
      database__client: mysql
      database__connection__host: db
      database__connection__port: "3306"
      database__connection__user: ghost
      database__connection__password: ${DATABASE_PASSWORD:?set DATABASE_PASSWORD in .env}
      database__connection__database: ghost
      mail__transport: SMTP
      mail__options__host: ${SMTP_HOST}
      mail__options__port: "465"
      mail__options__secure: "true"
      mail__options__auth__user: ${SMTP_USER}
      mail__options__auth__pass: ${SMTP_PASSWORD}
      mail__from: ${MAIL_FROM}
    volumes:
      - ghost_content:/var/lib/ghost/content
    networks:
      - ghost_net

  db:
    image: mysql:8.0.44
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ghost
      MYSQL_USER: ghost
      MYSQL_PASSWORD: ${DATABASE_PASSWORD:?set DATABASE_PASSWORD in .env}
      MYSQL_ROOT_PASSWORD: ${DATABASE_ROOT_PASSWORD:?set DATABASE_ROOT_PASSWORD in .env}
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-p${DATABASE_ROOT_PASSWORD}"]
      interval: 5s
      start_period: 30s
      retries: 20
    networks:
      - ghost_net

  caddy:
    image: caddy:2
    restart: unless-stopped
    depends_on:
      - ghost
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - ghost_net

networks:
  ghost_net:

volumes:
  ghost_content:
  mysql_data:
  caddy_data:
  caddy_config:

As variáveis de ambiente com duplo underscore (database__connection__host) são a convenção do próprio Ghost para sobrescrever seu config JSON aninhado sem tocar em nenhum arquivo, documentada na referência de configuração do Ghost. O url precisa ser o endereço https:// real desde o início: o Ghost grava isso nos links gerados e nos temas, e mudar depois exige um ghost config completo e rebuild de cache em vez de uma edição rápida.

Ao lado do arquivo Compose, crie um .env com valores reais:

DOMAIN=blog.example.com
DATABASE_PASSWORD=a-long-random-string
DATABASE_ROOT_PASSWORD=a-different-long-random-string
SMTP_HOST=smtp.postmarkapp.com
SMTP_USER=your-smtp-username
SMTP_PASSWORD=your-smtp-password
MAIL_FROM="Your Blog <hello@blog.example.com>"

Gere as duas senhas em vez de digitá-las à mão:

openssl rand -hex 24

Você deve receber uma string hexadecimal de 48 caracteres cada vez que rodar; use uma para DATABASE_PASSWORD e outra para DATABASE_ROOT_PASSWORD.

Etapa 3: Escreva o Caddyfile

O Caddy solicita e renova o certificado automaticamente assim que conseguir responder nas portas 80 e 443 para o seu domínio:

blog.example.com {
	reverse_proxy ghost:2368
}

É o arquivo inteiro. O desafio ACME do Caddy precisa da porta 80 aberta para a internet pública mesmo que o tráfego final vá todo por HTTPS, então não bloqueie ela no firewall.

Etapa 4: Suba a stack

docker compose up -d
docker compose logs -f ghost

Você deve ver o health check do MySQL passar, e depois o Ghost logar uma linha como Ghost is running in production... seguida da URL em que está servindo. Acesse https://blog.example.com/ghost para cair no assistente de configuração e criar sua primeira conta de admin.

Etapa 5: Faça backup do MySQL e do volume de conteúdo

Os dados reais do Ghost vivem em dois lugares: no MySQL (posts, membros, configurações) e no volume ghost_content (temas, imagens, uploads). Faça backup dos dois juntos para que uma restauração fique consistente:

#!/usr/bin/env bash
set -euo pipefail
STAMP=$(date +%Y%m%d-%H%M%S)
mkdir -p backups
docker compose exec -T db mysqldump -u root -p"$DATABASE_ROOT_PASSWORD" ghost | gzip > "backups/ghost-db-$STAMP.sql.gz"
docker run --rm -v ghost_content:/content -v "$(pwd)/backups:/backup" alpine \
  tar czf "/backup/ghost-content-$STAMP.tar.gz" -C /content .

Rode a partir do mesmo diretório do seu arquivo Compose (para que ghost_content resolva para o volume certo do projeto) e coloque num cron job diário. Copie o diretório backups/ para fora do próprio servidor — um rsync agendado para outra máquina ou um pequeno bucket de object storage já resolve; um backup que só vive no servidor que ele deveria proteger não é um backup.

Você deve ver dois arquivos novos em backups/ depois de cada execução, e o tamanho deles deve acompanhar o crescimento do seu conteúdo ao longo do tempo, não ficar zerado.

Etapa 6: Upgrades

Baixe a nova imagem e recrie só o container do Ghost; o MySQL e o volume de dados dele ficam intactos:

docker compose pull ghost
docker compose up -d ghost

O Ghost roda suas próprias migrações de banco no boot, então confira os logs em busca de uma linha confirmando que a migração terminou antes de considerar o upgrade concluído:

docker compose logs --tail=50 ghost

Faça um backup (Etapa 5) antes de cada upgrade, especialmente entre uma versão maior como a 5 para a 6, já que é exatamente esse tipo de mudança em que uma migração pode falhar no meio do caminho.

O caminho curto: faça com o Railyard

Tudo isso acima — os dois containers, a ligação entre eles, o proxy reverso e o certificado — é o que o template Ghost + MySQL do Railyard configura para você a partir do catálogo de templates. Ele roda ghost:5.130.6-alpine com um sidecar mysql:8.0.39: RAILYARD_URL e a string de conexão do MySQL já vêm ligadas automaticamente, então não existe database__connection__host para digitar à mão, e um volume ghost-content mais um ghost-db-data são criados e têm backup feito sem você precisar escrever um cron job.

Abra https://app.railyard.run/deploy?stack=ghost-mysql e escolha Ghost + MySQL no catálogo. Você ainda adiciona suas próprias credenciais de SMTP para o e-mail (o Railyard não fornece um transporte de e-mail), mas o banco, os volumes e o certificado HTTPS já estão resolvidos. Veja Deploy to Railyard button se preferir publicar esse mesmo fluxo de um clique a partir do seu próprio README, e Databases and backups para entender como backups e restaurações agendadas funcionam depois que estiver no ar.

Solução de problemas

Porta 80 ou 443 já em uso. Alguma outra coisa (geralmente uma instalação padrão de Apache ou nginx) já está vinculada a ela. sudo ss -tlnp | grep -E ':80|:443' mostra o quê, e você pode parar ou mover esse serviço antes de o Caddy conseguir ocupar essas portas.

O certificado nunca é emitido. Os logs do Caddy vão mostrar falhas repetidas de ACME. A causa quase universal é DNS: dig +short blog.example.com precisa retornar o IP do servidor antes de o Caddy conseguir comprovar a posse ao Let’s Encrypt. Confira também se nada upstream (um firewall de nuvem, um security group padrão do provedor) está bloqueando a porta 80 de entrada.

O Ghost fica reiniciando e os logs mostram erro de conexão com o MySQL. database__connection__host precisa corresponder ao nome do serviço no Compose (db no arquivo acima), e a senha no ambiente do Ghost precisa ser exatamente a mesma string que MYSQL_PASSWORD no serviço db. Uma senha com $ ou # precisa estar entre aspas de forma consistente nos dois lugares, ou escapada, já que tanto o Compose quanto o shell tentam interpretar esses caracteres.

Permissão negada ao escrever no volume de conteúdo. Isso aparece depois de restaurar um backup feito com tar como root dentro de um volume que o container do Ghost espera ser dono como seu próprio usuário. Restaure num volume nomeado novo com docker run --rm -v ghost_content:/content alpine tar xzf /backup/ghost-content-TIMESTAMP.tar.gz -C /content em vez de copiar arquivos com sua própria conta de usuário.

O MySQL fica sem memória num servidor de 1 GB. Os valores padrão do buffer pool do MySQL escalam com o tamanho da máquina, e num droplet de 1 GB sobra pouco espaço para o Ghost e o Node. É por isso que o droplet de US$18/mês com 2 GB acima é o mínimo real, não o menor tamanho listado.

Perguntas frequentes

Posso usar SQLite em vez de MySQL? Sim, a instalação padrão via CLI do Ghost ainda usa SQLite, e isso funciona bem de verdade para um site único com pouco tráfego. O MySQL compensa a complexidade quando você quer rodar mais de uma instância do Ghost contra os mesmos dados, ou está migrando de um host que já oferece MySQL gerenciado.

Isso dá suporte às newsletters e aos recursos de membership do Ghost? Sim, uma vez que o SMTP (para e-mail transacional) e um provedor de envio em massa (o Ghost se integra com o Mailgun especificamente para o envio de newsletter) estejam configurados. São duas configurações separadas no admin do Ghost, fácil de confundir porque as duas são “e-mail”.

Posso rodar o Ghost sem Docker? O Ghost oferece suporte a uma instalação nativa em Node.js no Ubuntu, documentada em docs.ghost.org, com o MySQL instalado como pacote do sistema em vez de container. O caminho via Docker neste guia é mais fácil de fazer backup e mover entre servidores, e foi por isso que usamos ele aqui.

Como migro um site Ghost existente para essa configuração? Use o próprio export/import do Ghost (Settings → Migration options) para o conteúdo, ou restaure um mysqldump direto no banco do container db se estiver migrando de outra instalação com MySQL, depois copie o diretório content/images antigo para o novo volume ghost_content.

O que acontece se o servidor reiniciar? restart: unless-stopped em cada serviço faz o Docker trazer os containers de volta automaticamente assim que o próprio daemon do Docker iniciar, o que acontece no boot por padrão numa distribuição baseada em systemd como o Ubuntu.

Qualquer um dos dois caminhos te dá um site Ghost que você realmente controla: o manual se você quiser ver cada peça se movendo, o template se preferir começar a escrever hoje mesmo.

Por que escolhem o Railyard para isso

  • Sua própria conta de cloud. Faça deploy no DigitalOcean, Hetzner, Vultr, AWS, OVHcloud, ou em qualquer servidor Linux via SSH, cobrado direto pelo seu provedor, sem markup.
  • Um preço fixo por equipe. O Free é grátis para sempre: um servidor mais um servidor de banco de dados, três apps, sem cartão. O Pro custa US$19 por mês para até cinco servidores e apps ilimitados. O Team custa US$49 por mês com servidores ilimitados. Sem cobrança por app, por deploy, por assento ou por gigabyte.
  • Templates de um clique. Faça deploy de apps open source, como bancos de dados, analytics e ferramentas de dev, no seu próprio servidor em um clique.
  • Bancos de dados gerenciados. Postgres, MySQL e Redis com backups programados, verificados por restauração, e recuperação point-in-time.
  • Push-to-deploy. Faça deploy a partir do GitHub, GitLab ou da CLI, com HTTPS automático e releases sem downtime.

Suba o Ghost + MySQL com um clique

Escolha Ghost + MySQL no catálogo de templates do Railyard e vá direto para escrever seu primeiro post.

Plano grátis, sem cartão, e 14 dias de Pro para cada equipe nova. Um preço fixo por equipe.