[TUTORIAL] Deploy para iniciantes: Como montar um fluxo Git (Maquina Local, repositorio Hub e pasta Producao)
Voce ja suou frio na hora de mandar uma atualizacao para o servidor ?
Ou pior, ja editou um arquivo via SSH direto na producao, esquecendo de salvar na sua maquina local e perdeu tudo numa atualizacao seguinte ?
O DEV que nunca detonou alguma alteracao assim que atire a primeira pedra.
Hoje vou compartilhar uma topologia de deploy que considero pratica para o dia-a-dia.
Ela eh simples, nao depende de pipelines complexas (como GitHub Actions) e te ajuda no controle do codigo.
O cenario inicial nao usa GitHub, mas nada impede de adicionar depois (inclusive mostro como fazer isso).
Fase 1. O Circuito Fechado (Local -> Hub -> Producao)
Vamos dividir a arquitetura inicial em tres partes:
-
Local: A sua maquina, onde voce faz o desenvolvimento, testa e quebra as coisas sem medo.
-
Hub (Servidor): Um repositorio Git que fica dentro da sua VPS (ex: na pasta
~/projetos/meu-app). Ele serve como a "porta de entrada" das suas atualizacoes. -
Producao: A pasta publica onde o seu app roda para o usuario final (ex:
/var/www/html/meu-app).
Notou que nao tem GitHub no processo ? Depois falamos dele (Fase 2).
1.1. O Fluxo proposto
Voce desenvolve tudo local, normal.
Ficou satisfeito ? Beleza, agora manda (via git) para o Hub (que fica no servidor) e auto-magicamente ele entra em producao no diretorio real do app.
1.2. Criando o repositorio Hub do servidor
A primeira coisa eh preparar o terreno no servidor.
Acesse sua VPS via SSH, crie a pasta do projeto e inicie o Git la dentro com uma configuracao especial para aceitar o push:
mkdir -p ~/projetos/meu-app
cd ~/projetos/meu-app
git init
git config receive.denyCurrentBranch updateInstead
Notou que nao tem --bare no git init ? Depois falamos dele (item 2.4.1.).
1.3. Atrelando teu desenvolvimento local ao Hub remoto
Vai na pasta do teu projeto local (na sua maquina) e configure o Hub (do servidor) como destino principal:
git remote add origin usuario@ip-do-servidor:~/projetos/meu-app
Quando a feature ta pronta e testada, voce simplesmente roda:
git push origin main
NOTA: Em alguns casos ao inves de
mainpode sermaster.
1.4. O "Hook" Magico no Servidor que leva do Hub para producao
Qual a magica para o que foi enviado para o Hub ir direto para producao ?
Usando Git Hooks, que permite executar scripts automaticamente apos receber um push.
Vai na pasta do Hub do servidor, e dentro dele na pasta .git/hooks
Crie um arquivo chamado post-receive (ou seja .git/hooks/post-receive)
O script nao tem muita firula (mas voce pode incrementar conforme a necessidade):
#!/bin/bash
# Onde os arquivos do site vao ficar visiveis para o publico
TARGET="/var/www/html/meu-app"
echo "Inicio deploy"
# Forca a pasta alvo a extrair os arquivos exatos que vieram no push
git --work-tree=$TARGET checkout -f
# Opcional: Rodar comandos especificos, como build
# cd $TARGET && npm run build
echo "Deploy concluido com sucesso !"
Nao esqueca de rodar
chmod +x post-receivepara dar permissao de execucao ao script
NOTA: caso de erro
Fatal: not a git repository, pode ser necessario colocar o--git-dirde forma explicita, neste caso use:
GIT_DIR="/home/usuario/projetos/meu-app/.git"
git --work-tree=$TARGET --git-dir=$GIT_DIR checkout -f
1.5. E agora ?
So isso.
Edita local, comita e push.
No VSCode, apos comitar (local) ele aparece a opcao sync changes que faz o push.
Fase 2. Colocando o GitHub na jogada
O projeto cresceu, voce quer compartilhar... uma opcao eh adicionar o GitHub.
Nesta topologia o GitHub entra apenas como um espelho paralelo, sem quebrar ou atrasar o seu deploy de producao.
Seu ambiente Local continua sendo o centro de tudo.
No seu terminal local, como o nome origin ja esta sendo usado pelo seu servidor de producao, voce adiciona o GitHub como um remote secundario (vamos chamar de meugithub):
# Adiciona o GitHub como seu espelho
git remote add meugithub https://github.com/seu-usuario/seu-projeto.git
Sua rotina de trabalho ganha dois caminhos independentes:
-
Quer botar a versao nova no ar ? (ou o padrao do sync do VSCode)
git push origin main (Manda pro Hub e atualiza a Producao). -
Fez o backup do dia ou quer abrir um Pull Request ?
git push meugithub main (Manda pro GitHub).
Se o GitHub (ou GitLab) ficarem fora do ar, voce continua publicando normalmente, pois o caminho Local -> Hub eh direto maquina a maquina.
NOTA: Por padrao no VSCode a opcao
sync changesque faz opushenvia para oorigin. Se quiser ter mais controle, use o terminal.
2.1. O detalhe crucial: .gitignore
O .gitignore é teu amigo, coloque nele os arquivos ou diretorios que NAO DEVEM IR PARA O REPOSITORIO HUB/PRODUCAO !!!
Por exemplo voce nao quer mandar o .env para o GitHub, entao adicione ele no .gitignore do seu ambiente Local: que eh de onde voce envia para o GitHub.
Eu costumo tambem colocar coisas que nao devem ir para GitHub no .gitignore do Hub por precaucao (caso em algum momento precisar enviar do Hub para o GitHub)
2.2. Alguem fez uma Pull Request no GitHub, e agora ?
O GitHub passa a ser o seu "ponto de integracao".
Se alguem (um colaborador ou ate voce mesmo em outro computador) abrir um PR no GitHub, eh so fazer este "triangulo":
-
Revisao e Merge: Voce entra no site do GitHub, revisa o codigo e clica no botao "Merge Pull Request". Agora, o codigo atualizado esta no GitHub, mas a sua maquina Local e o seu Servidor (Producao) ainda estao desatualizados.
-
Sincroniza o Local: No seu terminal local, voce puxa as novidades do GitHub:
git pull meugithub main
- Deploy para Producao: Agora que a sua maquina local tem o codigo do PR voce pode testar localmente para garantir que nada quebrou, e so entao envia para o servidor:
git push origin main
Resumo da opera: O PR entra pelo GitHub -> Desce para o seu PC -> Sobe para a Producao. O seu ambiente Local continua sendo o "ponto focal" do trabalho.
2.3. Como faco para o VSCode usar o GitHub no sync changes ?
Para mudar isso e fazer o VSCode usar o GitHub (meugithub) como destino padrao do botao sync changes, rode no seu terminal local:
git push -u meugithub main
A flag -u significa --set-upstream e ela diz pro Git: "A partir de agora, o padrao desta branch local eh o meugithub".
O que acontece depois de rodar isso?
Quando voce clicar no "Sync Changes" no VSCode, ele vai enviar (push) e puxar (pull) do GitHub
Para mandar para a producao, voce tera que abrir o terminal e digitar manualmente:
git push origin main
Se quiser voltar a usar o servidor de producao como destino principal do botao Sync, basta rodar:
git push -u origin main
2.4. Aviso aos navegantes
Para varias situacoes, as sugestoes que dei ate aqui podem parecer inadequadas.
Vamos aos apontamentos as mais comuns:
2.4.1. O Hub nao poderia ter sido inicializado com git init --bare ?
Sim, eh verdade - e ai nao precisa de updateInstead. Entretanto, contudo, todavia, eu gosto da ideia do repositorio Hub ter a mesma estrutura do projeto.
2.4.2. Usando checkout -f sozinho nao tem risco de ficarem arquivos orfaos antigos ?
Eventualmente pode acontecer. Alguns autores recomendam:
git --git-dir="$GIT_DIR" --work-tree="$TARGET" reset --hard
git --git-dir="$GIT_DIR" --work-tree="$TARGET" clean -fdn > /var/log/deploy_orfaos.log
git --git-dir="$GIT_DIR" --work-tree="$TARGET" clean -fd
Prefiro diff e limpar manualmente (mas pode ser inviavel em projetos grandes...)
O grande perigo do git clean -fd eh que ele confia cegamente no seu .gitignore.
Se alguem na equipe (ou voce num dia de pressa) remover a pasta de uploads (p.ex. storage) do .gitignore local e der o push, o git clean no servidor vai olhar e dizer: "Opa, isso aqui nao ta ignorado e nao ta no repositório. Vou apagar !" e la se vao os arquivos dos seus clientes - pois eh...
Uma alternativa seria este codigo no post-receive usando rsync:
#!/bin/bash
TEMP_DIR="/var/www/meu-app-temp"
TARGET="/var/www/html/meu-app"
# 1. Joga o codigo novo numa pasta limpa e segura
git --work-tree=$TEMP_DIR checkout -f main
# 2. Sincroniza apagando os orfaos, mas ignorando diretorios especificos: USE COM CUIDADO !!!
rsync -av --delete \
--exclude='.env' \
--exclude='storage/' \
--exclude='node_modules/' \
$TEMP_DIR/ $TARGET/
Outra alternativa eh o deploy
atomico(item 2.4.5. abaixo)
2.4.3. E as branchs ?
Sim, se tiver desenvolvimento em multiplas branchs, cabem algumas analises extras (p.ex. checkout apenas da main).
2.4.4. O uso de origin e meugithub vai contra as convencoes ?
De fato alguns autores recomendam:
origin repositorio principal (ou canonico) (normalmente GitHub/GitLab)
deploy repositorio de producao (~/projetos/meu-app em usuario@ip-do-servidor)
Aqui eu bato o pe: nao gosto da ideia do GitHub como principal, mas entendo este tipo de uso em projetos grandes.
2.4.5. Deploy atomico (e rollback por release)
Ao inves de fazer o checkout direto para a pasta de producao, uma opcao eh usar uma pasta de releases, por exemplo:
- extrai nova release em pasta nova (/var/www/releases/20260325-143000)
- roda build
- poe links de arquivos e/ou diretorios estaticos entre releases (.env, storage)
- valida
- troca symlink current (ln -s /var/www/releases/20260325-143000 /var/www/html/meu-app)
- voltar rollback = voltar symlink
Concordo que em projetos grandes faz sentido.
2.4.6. E se precisar fazer um Rollback via git ?
Liste os commits, pega o ID e revert
git log --oneline
git revert <commit>
git push origin main
2.4.7. Ta maluco em sugerir rodar cd $TARGET && npm run build ?
Sim, existem riscos: lixo, build parcial, dependencias.
Usar o Deploy atomico (item 2.4.5. acima) ou pelo menos uma pasta intermediaria para build.
2.4.8. Como faco para nao precisar usar usuario/senha no git remote ?
Na sua maquina (p.ex. linux)
/etc/ssh/ssh_config
host meurepo
HostName servidor.com.br
User usuario
IdentityFile ~/.ssh/id_ed25519
E coloque no servidor (/home/usuario/.ssh/authorized_keys) o conteudo de ~/.ssh/id_ed25519.pub
Ai da para usar:
git remote add origin meurepo:~/projetos/meu-app
2.4.9. Nao era melhor usar um pipeline mais robusto ?
Afinal, do jeito que esta:
-
producao recebe push direto
-
pouca governanca
-
sem aprovacao automatica
-
sem testes automatizados
-
sem trilha forte de release
Sim, tudo isso eh verdade: esse fluxo funciona para projetos pequenos, VPS simples, sites pessoais, apps internos ou MVPs. Para projetos maiores o ideal evoluir para CI/CD, build reprodutivel, releases e ambientes separados.
2.4.10. Exemplo de uma topologia mais purista
- servidor (com bare)
mkdir -p /home/usuario/repos/meu-app.git
cd /home/usuario/repos/meu-app.git
git init --bare
- na sua maquina (usando o GtHub como principal/origin e
deploydo servidor)
git remote add deploy deploy@meu-servidor:/home/usuario/repos/meu-app.git
git remote add origin [email protected]:seu-usuario/meu-app.git
- hook post-receive (basico com clean)
#!/bin/bash
set -e
GIT_DIR="/home/deploy/repos/meu-app.git"
TARGET="/var/www/meu-app"
BRANCH="refs/heads/main"
while read oldrev newrev refname; do
if [ "$refname" = "$BRANCH" ]; then
echo "Deploy da main iniciado..."
short_sha=$(echo "$newrev" | cut -c1-8)
release_name="$(date '+%Y%m%d%H%M%S')-$short_sha"
mkdir -p "$TARGET"
git --git-dir="$GIT_DIR" --work-tree="$TARGET" checkout -f main
git --git-dir="$GIT_DIR" --work-tree="$TARGET" clean -fdn > /var/log/deploy_orfaos${release_name}.log
git --git-dir="$GIT_DIR" --work-tree="$TARGET" clean -fd
# Exemplo:
# cd "$TARGET"
# npm run build
echo "Deploy concluído."
fi
done
- hook post-receive (basico com release/symlink)
#!/bin/bash
set -euo pipefail
GIT_DIR="/home/usuario/repos/meu-app.git"
APP_ROOT="/var/www/meu-app"
RELEASES_DIR="$APP_ROOT/releases"
SHARED_DIR="$APP_ROOT/shared"
CURRENT_LINK="$APP_ROOT/current"
BRANCH="refs/heads/main"
while read -r oldrev newrev refname; do
if [ "$refname" = "$BRANCH" ]; then
mkdir -p "$RELEASES_DIR"
mkdir -p "$SHARED_DIR"
short_sha=$(echo "$newrev" | cut -c1-8)
release_name="$(date '+%Y%m%d%H%M%S')-$short_sha"
release_path="$RELEASES_DIR/$release_name"
mkdir -p "$release_path"
git --git-dir="$GIT_DIR" archive "$newrev" | tar -x -C "$release_path"
# para cada arquivo/pasta no SHARED_DIR, cria um link na release
find "$SHARED_DIR" -mindepth 1 -maxdepth 1 | while read -r shared_path; do
item_name="$(basename "$shared_path")"
release_item="$release_path/$item_name"
rm -rf "$release_item"
ln -sfn "$shared_path" "$release_item"
done
# cd "$release_path"
# npm ci
# npm run build
# if teste automatizado OK
ln -sfn "$release_path" "$CURRENT_LINK"
# fi
fi
done
Assim, se precisar voltar um release:
- releases disponiveis
ls -1dt /var/www/meu-app/releases/*
- retorna aquele especifico
ln -sfn /var/www/meu-app/releases/20260325142001-9f8e7d6c /var/www/meu-app/current
Conclusao
Nao eh uma estrutura sofisticada, mas da para o gasto do dia-a-dia.
Eh possivel criar algo mais complexo, digamos um ambiente intermediario de homologacao contendo um espelho do que tem em producao e talvez com arquivos de dados reduzido para fazer testes mais elaborados/automatizados e deploy atomico (spoiler: eu costumo fazer algo semelhante com projetos mais casca-grossa).
NOTA: Claro que vao sugerir outros fluxos nos comentarios, e de boa, a ideia eh exatamente essa: pega um insight, olha o que serve para teu caso e adapta, transforma, expande, melhora. Nem de longe este eh o 'the best' ou unico fluxo, eh so o primeiro passo. Por exemplo, nao gosto de mandar tudo para o GitHub direto, por isso uso um
Hubmeu para controle e so envio para o GitHub depois de maturado, mas ja vi fluxos que mandam direto para la.
Espero que sirva de inspiracao no uso do dia-a-dia.
Saude e Sucesso !