Deploy do Magento 2 sem downtime em um único servidor
O jeito padrão como muitas lojas Magento são publicadas é assim: ativar maintenance mode, puxar o código, rodar setup:upgrade, setup:di:compile e setup:static-content:deploy, desativar o maintenance. A loja fica fora do ar durante todo o build, que pode levar vários minutos, e se a compilação falha no meio a loja continua fora até alguém consertar.
Você não precisa de Kubernetes nem de Adobe Commerce Cloud para evitar isso. Um único servidor consegue fazer deploys sem downtime com um padrão mais antigo que o Magento: construir cada release na sua própria pasta e trocar um symlink quando estiver pronta. Este artigo descreve esse padrão para Magento Open Source e Adobe Commerce 2.4.
A estrutura de pastas
/var/www/shop/
current -> releases/20260110-1530
releases/
20260109-1000/
20260110-1530/
shared/
app/etc/env.php
pub/media/
pub/sitemap/
var/log/
var/report/
var/export/
var/import/
Cada release é um código-base completo do Magento. Tudo que precisa sobreviver entre releases fica em shared e é ligado por symlink em cada release: env.php, media, logs, reports e qualquer pasta onde os seus módulos gravam arquivos. O document root do web server aponta para /var/www/shop/current/pub.
Caches e sessões não devem ficar em disco dentro da release. Coloque no Redis ou Valkey. Senão toda troca começa com um cache vazio numa pasta nova e as sessões antigas se perdem.
Prepare o config.php para um build sem banco de dados
O setup:di:compile e o setup:static-content:deploy conseguem rodar sem tocar no banco de produção, mas o static content deploy precisa saber seus scopes, themes e locales. Faça o dump dessa configuração no app/etc/config.php uma vez e faça o commit:
bin/magento app:config:dump scopes themes i18n
Daí em diante, qualquer mudança em store views, themes ou locales precisa de um novo dump e commit, ou o build vai publicar arquivos estáticos para o conjunto errado.
Passo 1: construa a release
#!/usr/bin/env bash
set -euo pipefail
BASE=/var/www/shop
RELEASE=$BASE/releases/$(date +%Y%m%d-%H%M%S)
REF=${1:-main}
git clone --depth 1 --branch "$REF" [email protected]:shop/magento.git "$RELEASE"
cd "$RELEASE"
composer install --no-dev --prefer-dist --optimize-autoloader --no-interaction
ln -sfn $BASE/shared/app/etc/env.php app/etc/env.php
rm -rf pub/media var/log var/report
ln -sfn $BASE/shared/pub/media pub/media
ln -sfn $BASE/shared/var/log var/log
ln -sfn $BASE/shared/var/report var/report
php bin/magento setup:di:compile
php bin/magento setup:static-content:deploy -f --jobs=4 en_US de_DE
Tudo isso acontece enquanto a loja continua servindo a release anterior. Se o composer install ou a compilação falhar, o script para e nada muda para os clientes. Use os locales de que as suas store views e usuários admin realmente precisam. Um locale ausente significa arquivos estáticos ausentes naquela store.
Garanta que a release esteja em production mode. O modo fica guardado no env.php como MAGE_MODE, e como o env.php é compartilhado, defina uma vez com bin/magento deploy:mode:set production --skip-compilation.
Passo 2: decida se o banco precisa de upgrade
Esse é o passo que decide entre zero downtime e uma janela curta de maintenance. A maioria das releases não muda o banco. Rode a partir da nova release:
set +e
php bin/magento setup:db:status
DB_STATUS=$?
set -e
O comando sai com 0 quando schema e dados estão atualizados e com 2 quando um upgrade é necessário. Qualquer outro código é um erro e deve parar o deploy.
Sem mudanças no banco
Vá direto para a troca. O novo código roda contra o mesmo banco do antigo, que é exatamente o que ele foi testado para fazer.
Com mudanças no banco
Aqui você precisa de uma janela curta de maintenance, porque o código antigo não deve gravar nas tabelas enquanto elas mudam.
if [ "$DB_STATUS" -eq 2 ]; then
php $BASE/current/bin/magento maintenance:enable
php bin/magento setup:upgrade --keep-generated
fi
O --keep-generated é essencial. Sem ele, o setup:upgrade apaga o código gerado que você acabou de compilar e a release rodaria sem ele. O maintenance é ativado a partir da release antiga, porque é ela que está servindo o tráfego. A flag de maintenance fica em var/, e como a nova release tem o seu próprio var, ela entra no ar sem a flag assim que o symlink troca.
A janela agora dura o tempo do setup:upgrade, não o do build inteiro. Com mudanças de schema escritas para serem retrocompatíveis, muitos times pulam o maintenance até aqui, mas essa é uma decisão a tomar por release, depois de ler a saída do dry run.
Passo 3: troque o symlink de forma atômica
O ln -sfn remove o link antigo e cria um novo. Por um instante não existe link. O jeito atômico é criar um link temporário e renomeá-lo por cima do antigo:
ln -s "$RELEASE" $BASE/current.tmp
mv -T $BASE/current.tmp $BASE/current
O mv -T é do GNU coreutils e faz um único rename, que é atômico no mesmo filesystem.
Se você ativou o maintenance no passo 2, a release anterior ainda tem a flag na sua própria pasta var. Rode maintenance:disable lá depois da troca. Senão um rollback mais tarde traria a loja de volta em maintenance mode.
Passo 4: depois da troca
cd $BASE/current
php bin/magento cache:flush
sudo systemctl reload php8.3-fpm
supervisorctl restart all
- Limpe o cache para que a configuração e o layout cacheados pela release antiga sejam reconstruídos.
- Faça o reload do PHP-FPM. O OPcache guarda arquivos compilados por caminho, e com symlinks ele pode continuar servindo a release antiga por um tempo. Um reload limpa isso de forma graciosa, sem derrubar as requisições em andamento.
- Reinicie os consumers de queue e qualquer outra coisa que rode PHP de longa duração, senão eles mantêm o código antigo na memória.
- O cron usa
current/bin/magento, então a próxima execução pega o código novo automaticamente.
Nginx e o realpath
A configuração de exemplo do Nginx que vem com o Magento passa $document_root para o PHP. Com uma raiz em symlink, use $realpath_root, para que o PHP e o OPcache vejam o caminho resolvido da release e não misturem arquivos de duas releases:
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
Rollback
Se algo der errado depois da troca, aponte o current de volta para a release anterior com os mesmos dois comandos, limpe o cache e faça o reload do PHP-FPM. Isso leva segundos porque a release antiga ainda está em disco, totalmente compilada.
Existe um limite. O rollback só é limpo quando a release não mudou o banco, ou quando a mudança foi apenas aditiva. O Magento não desfaz mudanças de schema nem data patches quando você volta o código. Se a nova release removeu uma coluna, o código antigo pode quebrar. Esse é o principal motivo para tornar as mudanças de schema retrocompatíveis e deixar as mudanças destrutivas para uma release posterior.
Mantenha as últimas três a cinco releases e apague as mais antigas no fim de cada deploy. Cada release com código compilado e arquivos estáticos ocupa um espaço em disco considerável.
O que costuma dar errado
- Static content desencontrado. A versão publicada em
pub/static/deployed_version.txtpertence a cada release. Se um CDN ou o Varnish cacheia HTML da release antiga que aponta para caminhos estáticos antigos, esses arquivos ainda precisam existir. Manter a release anterior em disco e a assinatura de arquivos estáticos ativada cobre a maioria dos casos. - Arquivos gravados dentro da release. Um módulo que grava em
pub/ouvar/fora das pastas compartilhadas perde seus arquivos no próximo deploy. Ache esses caminhos e adicione emshared. - Divergência do config.php. Uma store view criada em produção que não está no
config.phpcommitado fica sem arquivos estáticos. Faça o dump e o commit depois de mudanças de configuração. - Permissões. Faça o build com o mesmo usuário que roda o PHP-FPM, ou corrija o ownership antes da troca. Arquivos de root em
generatedcausam erros que só aparecem na primeira requisição. - Config import. Se a release altera configurações guardadas no
config.phpe nenhumsetup:upgraderodou, aplique combin/magento app:config:importdepois da troca.
Ferramentas que implementam esse padrão
Você pode escrever o script você mesmo, como mostrado, o que deixa tudo visível. O Deployer tem uma recipe para Magento 2 que segue a mesma estrutura de releases e shared, e outras ferramentas fazem algo parecido. Seja qual for a que você usar, leia o que ela executa. Os princípios continuam os mesmos: construir fora do live, tocar no banco só quando precisar, trocar de forma atômica, manter a release anterior pronta.
FAQ
Dá para fazer deploy do Magento 2 sem maintenance mode?
Sim, quando a release não muda o banco. Construa o código numa pasta separada, compile e publique os arquivos estáticos lá, depois troque um symlink. Veja com setup:db:status se um upgrade é necessário.
O que o --keep-generated faz no setup:upgrade?
Ele impede que o setup:upgrade apague a pasta generated. Use quando você já rodou o setup:di:compile durante o build, senão perderia o código compilado logo antes de entrar no ar.
Preciso de um banco de dados para rodar o setup:static-content:deploy?
Não, se o app/etc/config.php contém as configurações de scopes, themes e i18n, que você cria com bin/magento app:config:dump scopes themes i18n. Isso permite fazer o build no servidor ou no CI sem tocar nos dados de produção.
Como faço rollback de um deploy do Magento?
Aponte o symlink current de volta para a release anterior, limpe o cache e faça o reload do PHP-FPM. Isso é seguro quando a release não mudou o schema de um jeito que o código antigo não aguente.
Sou Rodrigo Biazzi, desenvolvedor Magento certificado, e trabalho com Magento e Adobe Commerce há mais de 12 anos. Atendo como freelancer em Magento, de correções pontuais a projetos mais longos. Se esse é o tipo de problema que você tem na sua loja, fale comigo.
Artigos relacionados
Magento 2 em production mode: como mudar e fazer o deploy
O que o production mode do Magento 2 muda, como trocar com segurança e uma sequência de deploy para o 2.4.8 que tira compilação e static deploy do downtime.
Backup e restore no Magento 2: o que salvar e como
Como fazer backup e restore de uma loja Magento 2: dumps de banco que importam sem erro, mídia, env.php e a chave de criptografia, cópias offsite e restores testados.
env.php vs config.php no Magento 2 explicados
O que vai no app/etc/env.php e no config.php do Magento 2, como funciona a precedência de configuração, app:config:dump, --lock-env x --lock-config e segredos fora do Git.
Pipeline de CI/CD do Magento 2 com GitHub Actions
Monte um pipeline de CI/CD do Magento 2.4.8 no GitHub Actions: auth do Composer, coding standard, PHPStan, testes unitários, build sem banco e deploy atômico.