Clonar qualquer repositório do GitHub e hospedar em VPS
Guia completo do começo ao fim: conectar por SSH, instalar tudo, clonar o repositório (público ou privado), configurar variáveis de ambiente, build, PM2, Nginx, domínio, certificado SSL grátis, firewall, atualização normal, atualização forçada e diagnóstico de erros. Funciona para qualquer app Node/React/Next/TanStack.
USUARIO/REPO = repositório do GitHub · MEUAPP = nome do projeto e do processo PM2 · PORTA = porta interna (3000, 3001…) · MEUDOMINIO.COM.BR = seu domínio · SEU@EMAIL.COM = e-mail para o certificado.
O que você precisa antes de começar
- Uma VPS Ubuntu 22.04 ou 24.04 com acesso root (Hetzner, Contabo, DigitalOcean, Hostinger…).
- O IP público da VPS e a senha (ou chave SSH).
- A URL do repositório no GitHub (ex.:
https://github.com/USUARIO/REPO.git). - Opcional: um domínio já registrado (Registro.br, Namecheap, GoDaddy…).
- Se o repositório for privado: um Personal Access Token (PAT) do GitHub — ver Passo 7.
Descubra qual comando de build/start seu projeto usa abrindo o package.json do repositório (scripts build e start).
Conectar na VPS por SSH
Execute isso no seu computador (Windows/Mac/Linux), não dentro da VPS. Esse comando é o que entra na VPS remotamente. Depois de conectado, todos os comandos seguintes já rodam dentro do servidor.
No Windows use o PowerShell ou o Terminal; no Mac/Linux use o Terminal.
ssh root@IP_DA_VPSSe estiver usando outra porta SSH:
ssh -p 22022 root@IP_DA_VPSNa primeira conexão ele pergunta Are you sure you want to continue connecting? — digite yes e depois a senha (a senha não aparece enquanto você digita, é normal).
C:\Users\Voce> vira root@ubuntu:~#. A partir daí, você está dentro do servidor e pode executar os próximos passos.Atualizar o sistema e criar usuário
apt update && apt upgrade -y
apt install -y curl wget git unzip ufw build-essentialDefina o fuso horário (opcional, ajuda nos logs):
timedatectl set-timezone America/Sao_PauloOpcional — criar um usuário não-root (mais seguro):
adduser deploy
usermod -aG sudo deploy
su - deploydeploy, os comandos administrativos passam a exigir sudo na frente. Para simplificar, este guia segue como root.Instalar Git, Node.js, PM2 e Nginx
Node.js 20 LTS (funciona para a maioria dos projetos modernos):
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejsConferir versões:
node -v
npm -v
git --versionPM2 (mantém o app rodando e reinicia sozinho) e Nginx:
npm install -g pm2
apt install -y nginx
systemctl enable nginx
systemctl start nginxTeste abrindo http://IP_DA_VPS no navegador: deve aparecer a página “Welcome to nginx”.
Preparar a pasta do projeto
mkdir -p /var/www
cd /var/wwwTodo projeto ficará em /var/www/MEUAPP. Use sempre esse padrão para não se perder quando tiver vários apps na mesma VPS.
Clonar o repositório (público ou privado)
Repositório público:
cd /var/www
git clone https://github.com/USUARIO/REPO.git MEUAPP
cd /var/www/MEUAPPSupondo que o repositório do seu projeto seja https://github.com/SEU_USUARIO/igreja-conectada.git e queira instalá-lo na pasta /var/www/igreja-conectada:
cd /var/www
git clone https://github.com/SEU_USUARIO/igreja-conectada.git igreja-conectada
cd /var/www/igreja-conectada
ls -laDepois de clonar, o projeto estará pronto para receber o .env, as dependências e o build no Passo 8.
Repositório real para você treinar: https://github.com/jogosdaunidade/relatorio-estadual.git. Vamos instalar na pasta /var/www/relatorio-estadual:
cd /var/www
git clone https://github.com/jogosdaunidade/relatorio-estadual.git relatorio-estadual
cd /var/www/relatorio-estadual
ls -la
git log --oneline -5Se o repositório for privado, o comando acima não vai concluir: o Git vai parar e pedir credenciais no terminal, exatamente assim:
root@vps:/var/www# git clone https://github.com/jogosdaunidade/relatorio-estadual.git relatorio-estadual
Cloning into 'relatorio-estadual'...
Username for 'https://github.com': jogosdaunidade
Password for 'https://jogosdaunidade@github.com': ghp_9fK2xQ7bTmZ4rLpV1wYe8sNc3dHgJ6aU0Bq5Em Username digite seu usuário do GitHub. Em Password não use a sua senha — cole o token (PAT) gerado no Passo 7. Ele não aparece na tela enquanto você cola (fica invisível): cole e aperte Enter. O token acima (ghp_9fK2xQ7bTmZ4rLpV1wYe8sNc3dHgJ6aU0Bq5) é apenas um exemplo fictício do formato — use o seu.
Para não digitar nada, coloque o token direto na URL:
git clone https://ghp_9fK2xQ7bTmZ4rLpV1wYe8sNc3dHgJ6aU0Bq5@github.com/jogosdaunidade/relatorio-estadual.git relatorio-estadualClonar uma branch específica:
git clone -b main --single-branch https://github.com/USUARIO/REPO.git MEUAPPRepositório privado (com token na URL — rápido, porém o token fica salvo):
git clone https://SEU_TOKEN@github.com/USUARIO/REPO.git MEUAPPConferir se veio tudo:
ls -la
git log --oneline -5Token do GitHub (PAT) e Deploy Key SSH
A) Gerar o Personal Access Token
- Acesse
https://github.com/settings/tokens. - Generate new token (classic) → marque o escopo
repo. - Defina validade e clique em Generate token.
- Copie o token (começa com
ghp_) — ele só aparece uma vez.
B) Salvar o token na VPS (sem deixar na URL):
git config --global credential.helper store
cd /var/www/MEUAPP
git pull
# digite o usuário do GitHub e, na senha, cole o TOKENA partir daí o git pull não pede mais nada.
C) Alternativa mais segura — Deploy Key SSH:
ssh-keygen -t ed25519 -C "vps-MEUAPP" -f ~/.ssh/id_ed25519_MEUAPP -N ""
cat ~/.ssh/id_ed25519_MEUAPP.pubCopie a chave exibida e cole em GitHub → seu repositório → Settings → Deploy keys → Add deploy key (marque “Allow write access” só se for dar push da VPS).
cat >> ~/.ssh/config <<'EOF'
Host github.com
IdentityFile ~/.ssh/id_ed25519_MEUAPP
IdentitiesOnly yes
EOF
ssh -T git@github.comE troque a origem do repositório para SSH:
cd /var/www/MEUAPP
git remote set-url origin git@github.com:USUARIO/REPO.git
git pullVariáveis de ambiente (.env)
Quase todo projeto precisa de um arquivo .env na raiz. Veja se existe um .env.example no repositório e copie:
cd /var/www/MEUAPP
cp .env.example .env 2>/dev/null || touch .env
nano .envExemplo de conteúdo (ajuste ao seu projeto):
NODE_ENV=production
PORT=PORTA
VITE_PUBLIC_SITE_URL=https://MEUDOMINIO.COM.BR
VITE_SUPABASE_URL=https://xxxxx.supabase.co
VITE_SUPABASE_PUBLISHABLE_KEY=sua_chave_publicaSalve com Ctrl+O, Enter, e saia com Ctrl+X. Proteja o arquivo:
chmod 600 .env.env está no .gitignore do repositório.Instalar dependências e gerar o build
cd /var/www/MEUAPP
npm installSe o projeto tiver package-lock.json, o comando mais fiel ao lock é:
npm ciBuild de produção:
npm run buildPara projetos TanStack Start / Nitro, force o preset de servidor Node:
NITRO_PRESET=node-server npm run buildfallocate -l 2G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstabSubir a aplicação com PM2
Descubra o arquivo de entrada do build. Os mais comuns: .output/server/index.mjs (Nitro/TanStack), dist/server/entry.mjs, build/index.js ou server.js.
cd /var/www/MEUAPP
PORT=PORTA pm2 start ".output/server/index.mjs" --name MEUAPP
pm2 save
pm2 startupSe o projeto usa um script npm (ex.: npm start):
cd /var/www/MEUAPP
pm2 start npm --name MEUAPP -- start
pm2 saveSite estático (React/Vite sem servidor):
Não precisa de PM2 — o Nginx serve a pasta dist direto (veja a variação no Passo 11).
Conferir se está rodando:
pm2 status
curl -I http://127.0.0.1:PORTAConfigurar o Nginx (proxy reverso)
nano /etc/nginx/sites-available/MEUAPPCole o conteúdo abaixo (app Node rodando em uma porta):
server {
listen 80;
server_name MEUDOMINIO.COM.BR www.MEUDOMINIO.COM.BR;
client_max_body_size 20M;
location / {
proxy_pass http://127.0.0.1:PORTA;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}Ativar o site e recarregar:
ln -s /etc/nginx/sites-available/MEUAPP /etc/nginx/sites-enabled/MEUAPP
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl reload nginxVariação — site estático (pasta dist):
server {
listen 80;
server_name MEUDOMINIO.COM.BR;
root /var/www/MEUAPP/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}Apontar o domínio (DNS)
No painel do seu registrador (Registro.br, Cloudflare, GoDaddy…) crie os registros:
Tipo Nome Valor TTL
A @ IP_DA_VPS 3600
A www IP_DA_VPS 3600- Acesse registro.br e faça login com o CPF/CNPJ do titular.
- No menu Domínios → clique no domínio que você quer configurar (ex.:
meusite.com.br). - Clique em Editar Zona (ou Zona de DNS, se o painel estiver em português antigo).
- Apague registros antigos do tipo
AouCNAMEque apontem para @/www, para não conflitar com os novos. - Clique em Adicionar Registro e crie o primeiro:
- Tipo:
A - Nome: deixe em branco ou
@(significa o domínio raiz) - Conteúdo / Valor: o IP da VPS (ex.:
91.98.120.163) - TTL:
3600
- Tipo:
- Adicione o segundo registro:
- Tipo:
A - Nome:
www - Conteúdo / Valor: o mesmo IP da VPS
- TTL:
3600
- Tipo:
- Clique em Salvar e aguarde. No Registro.br a propagação costuma levar de 15 minutos a 4 horas, mas pode demorar até 24 horas em alguns casos.
@, deixe-o em branco. Em alguns painéis do Registro.br, em branco = raiz do domínio. Nunca coloque o domínio inteiro (ex.: meusite.com.br) no campo Nome — isso cria um subdomínio errado.Verifique a propagação (pode levar de minutos a algumas horas):
apt install -y dnsutils
dig +short MEUDOMINIO.COM.BR
dig +short www.MEUDOMINIO.COM.BRO resultado precisa ser o IP da sua VPS. Só siga para o SSL depois que o IP aparecer correto.
Certificado SSL grátis (HTTPS)
apt install -y certbot python3-certbot-nginxEmitir o certificado (Let's Encrypt) e configurar o Nginx automaticamente:
certbot --nginx -d MEUDOMINIO.COM.BR -d www.MEUDOMINIO.COM.BR --non-interactive --agree-tos -m SEU@EMAIL.COM --redirectO parâmetro --redirect força todo acesso HTTP a virar HTTPS. Testar a renovação automática:
certbot renew --dry-run
systemctl status certbot.timerVer certificados instalados e datas de expiração:
certbot certificatesDepois do SSL, ajuste a URL pública no .env e reinicie:
cd /var/www/MEUAPP
nano .env # VITE_PUBLIC_SITE_URL=https://MEUDOMINIO.COM.BR
npm run build
pm2 restart MEUAPP && pm2 saveFirewall (portas 80, 443 e app)
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
ufw status numberedAs portas internas do app (3000, 3001…) não precisam ficar abertas para a internet, pois o Nginx faz o proxy. Só libere se quiser testar direto pelo IP:
ufw allow 3000/tcp
ufw allow 3001/tcpTestar acesso:
curl -I http://127.0.0.1:PORTA
curl -I https://MEUDOMINIO.COM.BRAtualização normal (deploy do dia a dia)
Sempre que fizer alterações e der push no GitHub, rode na VPS:
cd /var/www/MEUAPP && git pull && npm install && npm run build && pm2 restart MEUAPP && pm2 savePara projetos TanStack/Nitro:
cd /var/www/MEUAPP && git pull && npm install && NITRO_PRESET=node-server npm run build && pm2 restart MEUAPP && pm2 saveCriar um script de atualização (recomendado):
cat > /usr/local/bin/deploy-MEUAPP <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
cd /var/www/MEUAPP
git pull
npm install
npm run build
pm2 restart MEUAPP
pm2 save
echo "Deploy concluído."
EOF
chmod +x /usr/local/bin/deploy-MEUAPPA partir de agora basta digitar deploy-MEUAPP de qualquer lugar.
Atualização forçada (quando nada resolve)
Use quando o site continua antigo, o build falha por cache, ou alguém editou arquivos direto na VPS gerando conflito de Git.
cd /var/www/MEUAPP
git fetch origin
git reset --hard origin/main
git clean -fd -e .env
rm -rf .output dist build node_modules/.vite node_modules/.cache
npm install
npm run build
pm2 delete MEUAPP || true
PORT=PORTA pm2 start ".output/server/index.mjs" --name MEUAPP
pm2 save
systemctl reload nginxgit reset --hard descarta qualquer alteração local e iguala ao GitHub · git clean -fd -e .env remove arquivos não versionados preservando o .env · rm -rf apaga os caches de build · pm2 delete mata o processo travado antes de recriar.Versão em uma linha só:
cd /var/www/MEUAPP && git fetch origin && git reset --hard origin/main && rm -rf .output dist node_modules/.vite && npm install && npm run build && pm2 restart MEUAPP && pm2 saveVários projetos na mesma VPS
Cada projeto recebe uma pasta, uma porta e um arquivo Nginx próprios. Exemplo com dois apps:
/var/www/app-um -> porta 3000 -> umsite.com.br
/var/www/app-dois -> porta 3001 -> outrosite.com.brRepita os passos 6 a 13 trocando pasta, porta e domínio:
cd /var/www
git clone https://github.com/USUARIO/OUTRO-REPO.git app-dois
cd app-dois && npm install && npm run build
PORT=3001 pm2 start ".output/server/index.mjs" --name app-dois
pm2 saveNginx do segundo domínio:
nano /etc/nginx/sites-available/app-dois
ln -s /etc/nginx/sites-available/app-dois /etc/nginx/sites-enabled/app-dois
nginx -t && systemctl reload nginx
certbot --nginx -d outrosite.com.br -d www.outrosite.com.br --agree-tos -m SEU@EMAIL.COM --redirectVer todos os processos e portas em uso:
pm2 list
ss -ltnp | grep -E ':3000|:3001'Logs, diagnóstico e erros comuns
Comandos de diagnóstico:
pm2 logs MEUAPP --lines 100
pm2 monit
tail -f /var/log/nginx/error.log
tail -f /var/log/nginx/access.log
systemctl status nginx
df -h # espaço em disco
free -m # memóriaVer quanto de espaço cada projeto ocupa na VPS:
du -sh /var/www/* | sort -rhEsse comando lista todas as pastas em /var/www do maior para o menor. Para detalhar um projeto específico (dependências e build separados):
du -sh /var/www/MEUAPP
du -sh /var/www/MEUAPP/node_modules /var/www/MEUAPP/.output /var/www/MEUAPP/dist 2>/dev/nullpm2 list mostra a memória RAM de cada processo, não o espaço em disco. O espaço em disco é por pasta (du -sh), e node_modules + .output costumam ser as partes maiores de cada projeto.Tabela de erros:
| Sintoma | Causa provável | Solução |
|---|---|---|
| 502 Bad Gateway | App Node caiu ou porta errada | pm2 restart MEUAPP e conferir proxy_pass |
| 404 em rotas internas | Falta try_files no Nginx (site estático) | Adicionar try_files $uri /index.html |
| Site antigo após deploy | Cache do navegador ou build velho | Ctrl+F5 e atualização forçada (Passo 16) |
| Username for github.com | Repositório privado sem credencial | Configurar PAT ou deploy key (Passo 7) |
| EADDRINUSE | Porta já ocupada | ss -ltnp | grep PORTA e matar o processo |
| Killed durante o build | Falta de RAM | Criar swap (Passo 9) |
| Certbot falha | DNS não propagado ou porta 80 fechada | Passos 12 e 14 |
| pm2 errored | Erro no código ou .env faltando | pm2 logs MEUAPP --lines 100 |
Backup e remoção do projeto
Backup do .env e do projeto:
mkdir -p /root/backups
cp /var/www/MEUAPP/.env /root/backups/MEUAPP.env.$(date +%F)
tar -czf /root/backups/MEUAPP-$(date +%F).tar.gz -C /var/www MEUAPP --exclude=node_modules --exclude=.outputBaixar o backup para o seu computador (rode no SEU PC, não na VPS):
scp root@IP_DA_VPS:/root/backups/MEUAPP-*.tar.gz .Remover completamente um projeto da VPS:
pm2 delete MEUAPP || true
pm2 save
rm -rf /var/www/MEUAPP
rm -f /etc/nginx/sites-enabled/MEUAPP /etc/nginx/sites-available/MEUAPP
nginx -t && systemctl reload nginx
certbot delete --cert-name MEUDOMINIO.COM.BRAtualização automática (comando único + cron)
Aqui você cria um script que faz tudo sozinho: git pull, npm install, build e restart do PM2. Depois, se quiser, agenda no cron para rodar automaticamente todo dia.
- USUARIO = seu usuário do GitHub
- REPO = nome do repositório no GitHub
- MEUAPP = nome da pasta e do processo PM2
Exemplo: se o repositório for https://github.com/jogosdaunidade/relatorio-estadual.git, substitua USUARIO/REPO por jogosdaunidade/relatorio-estadual e MEUAPP por relatorio-estadual.
1. Criar o script de atualização automática
cat > /usr/local/bin/auto-update-MEUAPP <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
APP_DIR="/var/www/MEUAPP"
APP_NAME="MEUAPP"
BRANCH="main"
LOG="/var/log/auto-update-$APP_NAME.log"
exec >> "$LOG" 2>&1
echo "===== $(date '+%F %T') iniciando atualização ====="
cd "$APP_DIR"
git fetch origin "$BRANCH"
LOCAL=$(git rev-parse HEAD)
REMOTE=$(git rev-parse "origin/$BRANCH")
if [ "$LOCAL" = "$REMOTE" ]; then
echo "Nada novo no GitHub. Nada a fazer."
exit 0
fi
echo ">> Novidades encontradas. Atualizando..."
git pull origin "$BRANCH"
npm install --no-audit --no-fund
NITRO_PRESET=node-server npm run build
pm2 restart "$APP_NAME" --update-env
pm2 save
echo ">> Atualização concluída com sucesso."
EOF
chmod +x /usr/local/bin/auto-update-MEUAPPTroque MEUAPP pela pasta e pelo nome do processo no PM2. Se o projeto não for TanStack/Nitro, use apenas npm run build.
2. Rodar manualmente (comando único)
auto-update-MEUAPPVersão em uma linha só, sem criar script:
cd /var/www/MEUAPP && git pull origin main && npm install --no-audit --no-fund && NITRO_PRESET=node-server npm run build && pm2 restart MEUAPP --update-env && pm2 saveTroque MEUAPP e main pelos seus valores reais.
3. Agendar no cron (opcional)
crontab -eAdicione UMA das linhas abaixo no final do arquivo e salve:
# todo dia às 03:00 da manhã
0 3 * * * /usr/local/bin/auto-update-MEUAPP
# a cada 30 minutos
*/30 * * * * /usr/local/bin/auto-update-MEUAPP
# a cada hora cheia
0 * * * * /usr/local/bin/auto-update-MEUAPPTroque MEUAPP pelo nome do processo PM2.
Conferir se o agendamento foi salvo e acompanhar o log:
crontab -l
tail -f /var/log/auto-update-MEUAPP.logPara desativar o agendamento depois:
crontab -e # apague a linha do auto-update e salveInstalar o Membresia 360 em outra VPS
Instalação manual em uma VPS nova com Ubuntu 24.04 LTS. Prefira 2 vCPUs, 4 GB de RAM e 40 GB de disco. O servidor atual, 91.98.120.163, não precisa ser alterado. Os comandos abaixo são para o servidor novo.
203.0.113.10, o domínio app.suaigreja.com.bre o e-mail admin@suaigreja.com.br são apenas exemplos. Use outro domínio: não mude o DNS de membresia360.com.br. Execute os blocos na ordem, um por vez, e pare se aparecer algum erro.1. Entrar na VPS nova
ssh root@IP_DA_VPS_NOVAIP_DA_VPS_NOVA = IP informado pelo provedor da nova VPS.
Exemplo de como fica depois da substituição:
ssh root@203.0.113.10Não envie senha ou chave privada SSH aqui. Os próximos comandos são executados dentro dessa conexão.
2. Definir os parâmetros da instalação
IP_NOVO="IP_DA_VPS_NOVA"
DOMINIO="SEU_DOMINIO"
EMAIL_SSL="SEU_EMAIL"
APP_DIR="/var/www/NOME_DA_PASTA"
APP_NAME="NOME_NO_PM2"
APP_PORT="PORTA_DO_APP"
REPO_URL="https://github.com/jogosdaunidade/igreja-conectada-360.git"IP_DA_VPS_NOVA: IP público da VPS nova.SEU_DOMINIO: domínio ou subdomínio, semhttps://e sem barra no final.SEU_EMAIL: e-mail real para avisos de renovação do certificado.NOME_DA_PASTA: nome da pasta onde os arquivos serão instalados.NOME_NO_PM2: nome do aplicativo na tabela do PM2, sem espaços.PORTA_DO_APP: use3000em uma VPS nova; se ocupada, escolha outra porta livre.REPO_URL: já contém o repositório deste aplicativo; mantenha para instalar o Membresia 360.
Exemplo completo — copie este bloco no lugar do anterior e troque os dados fictícios:
IP_NOVO="203.0.113.10"
DOMINIO="app.suaigreja.com.br"
EMAIL_SSL="admin@suaigreja.com.br"
APP_DIR="/var/www/membresia360"
APP_NAME="membresia360"
APP_PORT="3000"
REPO_URL="https://github.com/jogosdaunidade/igreja-conectada-360.git" /var/www/membresia360; os comandos com $APP_DIR entram na pasta escolhida.3. Instalar Node.js 22, Git, Nginx e PM2
apt update && apt upgrade -y && apt install -y curl ca-certificates git nginx ufw build-essential
curl -fsSL https://deb.nodesource.com/setup_22.x -o /tmp/nodesource-setup.sh && bash /tmp/nodesource-setup.sh && apt install -y nodejs
npm install -g pm2
node -v && npm -v && pm2 -vConfira que o Node mostra v22.12.0 ou uma versão 22 mais recente antes de continuar.
4. Baixar o aplicativo do GitHub
mkdir -p "$(dirname "$APP_DIR")" && git clone "$REPO_URL" "$APP_DIR" && cd "$APP_DIR"Se o repositório for privado, informe seu usuário GitHub quando solicitado. No campo de senha, cole um token GitHub válido com acesso ao repositório, não sua senha de login. Gere o token em github.com/settings/tokens. Não coloque o token na URL nem envie o valor aqui.
5. Configurar as chaves do Supabase
dyjeylwbxbcvpolnwutd conecta esta instalação aos mesmos dados do aplicativo atual; uma alteração afeta o mesmo banco. Para uma instalação independente, crie outro projeto Supabase e aplique as migrações do aplicativo antes de usar. Nunca coloque a chave secreta em uma variável com prefixo VITE_.Abra um arquivo protegido, dentro da pasta escolhida:
cd "$APP_DIR" && (umask 077; touch .env.local) && chmod 600 .env.local && nano .env.localCole o modelo abaixo no editor e substitua os valores em maiúsculas:
VITE_SUPABASE_URL="https://ID_DO_PROJETO.supabase.co"
VITE_SUPABASE_PROJECT_ID="ID_DO_PROJETO"
VITE_SUPABASE_ANON_KEY="CHAVE_PUBLICA_SUPABASE"
VITE_SUPABASE_PUBLISHABLE_KEY="CHAVE_PUBLICA_SUPABASE"
SUPABASE_URL="https://ID_DO_PROJETO.supabase.co"
SUPABASE_PUBLISHABLE_KEY="CHAVE_PUBLICA_SUPABASE"
SUPABASE_SERVICE_ROLE_KEY="CHAVE_SECRETA_SUPABASE"
VITE_PUBLIC_SITE_URL="https://SEU_DOMINIO"ID_DO_PROJETO: identificador do Supabase; exemplo do banco atual:dyjeylwbxbcvpolnwutd.CHAVE_PUBLICA_SUPABASE: chave pública/anon do projeto, nas configurações de API do Supabase.CHAVE_SECRETA_SUPABASE: chave secreta/service_role do mesmo projeto; mantenha somente no servidor.SEU_DOMINIO: o domínio definido no bloco 2; exemplo:app.suaigreja.com.br.
Salve com Ctrl+O, confirme com Enter e saia com Ctrl+X. Não envie foto desse arquivo com as chaves visíveis.
6. Preparar e iniciar o aplicativo
cd "$APP_DIR" && npm install --no-audit --no-fund && (
set -a
. ./.env.local
set +a
export PORT="$APP_PORT" HOST="127.0.0.1" NODE_ENV="production"
NITRO_PRESET=node-server npm run build && pm2 start .output/server/index.mjs --name "$APP_NAME" --update-env
) && pm2 saveAtive a inicialização automática depois de reiniciar a VPS (estes exemplos usam o usuário root):
pm2 startup systemd -u root --hp /root && pm2 saveConfira o aplicativo localmente antes de configurar o domínio:
pm2 list
curl -I "http://127.0.0.1:$APP_PORT"7. Configurar o Nginx
cat > "/etc/nginx/sites-available/$APP_NAME" <<EOF
server {
listen 80;
server_name $DOMINIO;
client_max_body_size 25M;
location / {
proxy_pass http://127.0.0.1:$APP_PORT;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
}
}
EOF
ln -sfn "/etc/nginx/sites-available/$APP_NAME" "/etc/nginx/sites-enabled/$APP_NAME" && nginx -t && systemctl reload nginxLibere SSH e acesso ao site. Se usa outra porta SSH, libere essa porta antes de ativar o firewall. Libere também 80/443 no painel do provedor, se houver firewall externo.
ufw allow OpenSSH && ufw allow 'Nginx Full' && ufw enableNão é necessário abrir a porta do aplicativo (3000) para a internet; o Nginx encaminha o acesso.
8. Apontar o domínio e ativar HTTPS
No painel DNS do seu domínio, crie um registro A. Exemplo fictício:
| Tipo | Nome | Valor |
|---|---|---|
| A | app | 203.0.113.10 |
Para app.suaigreja.com.br, o nome é app. Para o domínio principal, use @. Troque o IP pelo da VPS nova; remova registros conflitantes e não mantenha um AAAA apontando para outro servidor.
Confira se o IP retornado é o da VPS nova e aguarde a atualização do DNS:
getent ahostsv4 "$DOMINIO"
printf 'IP esperado: %s\n' "$IP_NOVO"Somente depois do DNS correto, emita o certificado:
apt install -y certbot python3-certbot-nginx && certbot --nginx -d "$DOMINIO" --agree-tos -m "$EMAIL_SSL" --redirect
certbot renew --dry-run9. Liberar o novo endereço no Supabase e testar
Em Supabase → Authentication → URL Configuration, adicione o novo endereço HTTPS às Redirect URLs (exemplo: https://app.suaigreja.com.br/**). Se usar o mesmo banco, mantenha a Site URL atual e os endereços existentes; em um projeto novo, defina a Site URL para o novo domínio. Google e outras integrações também precisam permitir o novo endereço quando utilizadas.
curl -I "https://$DOMINIO"
pm2 logs "$APP_NAME" --lines 50 --nostreamAbra o novo domínio, aperte Ctrl+F5, entre na sua conta e confira os membros e células. Não considere a instalação concluída somente porque o PM2 mostra online.
10. Atualizações futuras e proteção
Dentro da pasta escolhida (padrão /var/www/membresia360), use os mesmos nomes e porta da instalação:
cd "$APP_DIR" && sudo bash public/scripts/deploy.sh "$APP_DIR" "$APP_NAME" "$APP_PORT"Exemplo com a pasta e os nomes do bloco 2:
cd /var/www/membresia360 && sudo bash public/scripts/deploy.sh /var/www/membresia360 membresia360 3000Os backups e alertas não são ativados por esta instalação. Configure os destinos e sinais específicos da nova VPS seguindo o guia de proteção — Passo 36. O guia principal de instalação reúne os demais comandos operacionais. Após cada atualização, aperte Ctrl+F5.
Levar o app para outra hospedagem — o que serve e o que não serve
O Membresia 360 é um aplicativo Node.js: ele precisa de um servidor rodando 24h com Node, PM2 e Nginx — exatamente como hoje na 91.98.120.163. Por isso, não funciona em hospedagem compartilhada tradicional (planos de site com cPanel, PHP, HostGator, GoDaddy básico, Hostinger de site simples): essas apenas servem arquivos PHP/estáticos e não executam o servidor do app.
Tipos de hospedagem — o que funciona
- VPS / Cloud (recomendado): Ubuntu com Node.js, PM2 e Nginx. É o modelo atual. O procedimento completo é o Passo 21 deste guia — basta substituir os parâmetros (IP, domínio, pasta, porta).
- PaaS com Node (Render, Railway, Fly.io, DigitalOcean App Platform): não se "enviam arquivos": conecta-se o repositório do GitHub e a plataforma faz o build e roda sozinha. Funciona, mas costuma custar mais por mês que uma VPS.
- Hospedagem estática (Netlify, Vercel, Cloudflare Pages): só funcionaria convertendo o app para rodar 100% no navegador, o que exigiria reescrever partes do projeto — não recomendado.
- Hospedagem compartilhada com cPanel/PHP: não serve. O build gera um servidor Node (
.output/server/index.mjs), não páginas PHP.
Procedimento para mudar de servidor (VPS nova)
Use os blocos do Passo 21 na ordem:
# 1. Instale na VPS nova (Passo 21, blocos 1 a 9)
# 2. Teste o app no domínio novo antes de mexer no DNS
curl -I "https://SEU_DOMINIO_NOVO"
# 3. Confirme que tudo funciona: login, membros, células, relatórios
# 4. Só então mude o DNS do domínio para o IP da VPS nova
# (registro A: SEU_DOMINIO → IP_DA_VPS_NOVA)
# 5. Acompanhe a propagação do DNS
getent ahostsv4 SEU_DOMINIO
# 6. Depois da propagação, atualize o certificado SSL na VPS nova
sudo certbot --nginx -d SEU_DOMINIO --redirectO site continua no ar na VPS antiga até o DNS propagar (geralmente minutos a algumas horas). Por isso, instale e teste primeiro; mude o DNS por último.
membresia360.com.br já aponta para o servidor atual.Dúvida sobre qual provedor escolher? Qualquer VPS Ubuntu com pelo menos 2 vCPUs e 4 GB de RAM atende — os requisitos completos estão no Passo 21, bloco 1.
Instalar o Supabase auto-hospedado na mesma VPS
O Supabase também pode rodar dentro da sua VPS, no lugar do Supabase da nuvem (dyjeylwbxbcvpolnwutd). Ele sobe como um conjunto de serviços em Docker: banco de dados, login, armazenamento de arquivos, painel e e-mail. O Membresia 360 continua no mesmo lugar, na porta 3000 — o Supabase novo fica num endereço separado, por exemplo db.membresia360.com.br.
1. Conferir se a VPS aguenta
Rode na VPS e confira estes números antes de instalar qualquer coisa:
free -h && df -h / && nproc && uname -m && (docker --version || echo "sem docker")free -h: a linhaMemprecisa mostraravailablede pelo menos 2 GB.df -h /: pelo menos 15 GB livres em/.nproc: 2 ou mais núcleos.docker --version: se aparecersem docker, o Docker ainda não está instalado (bloco 3).uname -m: se apareceraarch64, a máquina é ARM — as imagens oficiais do Supabase têm versão ARM64 nas versões atuais, então o Supabase roda normalmente aí.
Hoje a VPS roda 6 sites e estava com 58% de memória em uso. Se sobrar menos de 2 GB, o Supabase auto-hospedado não é indicado — o Supabase da nuvem continua sendo a opção melhor.
Se a linha Swap: do free -h mostrar 0B (é o comum em VPS nova), crie um arquivo de swap antes de subir o Supabase: em máquina de 4 GB isso evita que o Docker seja derrubado por falta de memória no meio da instalação.
fallocate -l 2G /swapfile || dd if=/dev/zero of=/swapfile bs=1M count=2048
chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
free -h2. Definir os parâmetros
DB_DIR="/opt/supabase"
DB_DOMAIN="db.membresia360.com.br"
EMAIL_SSL="neuzinaldo@gmail.com"
APP_DIR="/var/www/membresia360"
APP_NAME="membresia360"
APP_PORT="3000"DB_DIR: pasta onde os arquivos do Supabase vão ficar (fora de/var/www, que é servido pelo Nginx).DB_DOMAIN: subdomínio exclusivo para o Supabase; não usemembresia360.com.brnemwww.EMAIL_SSL: e-mail real, usado para avisos de renovação do certificado.- Os três últimos só servem para conferir conflito de porta e, depois, reapontar o aplicativo.
Mantenha a mesma conexão SSH durante todo o passo a passo. Se desconectar, entre de novo e repita este bloco com os mesmos valores.
3. Instalar Docker e o plugin de composição
apt update && apt upgrade -y
apt install -y ca-certificates curl git
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list
apt update && apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
docker --version && docker compose versionConfira as duas versões no final. Se o docker compose version der erro, a instalação não foi concluída — não continue.
4. Baixar os arquivos oficiais do Supabase
mkdir -p /opt && cd /opt
git clone --depth 1 https://github.com/supabase/supabase.git /opt/supabase-src
cp -r /opt/supabase-src/docker "$DB_DIR" && cd "$DB_DIR" && rm -rf docker-compose.s3.yml
ls -1Você deve ver docker-compose.yml, .env.example e a pasta volumes. Esses arquivos vêm do repositório oficial do Supabase; são eles que definem os serviços.
5. Gerar as chaves próprias (senhas e tokens)
O Supabase auto-hospedado não aceita as chaves do projeto atual. Cada instalação precisa de uma senha de banco, de um segredo de assinatura e de duas chaves de acesso criadas por você. Gere assim (dentro de $DB_DIR):
cd "$DB_DIR"
export POSTGRES_PASSWORD="$(openssl rand -base64 32 | tr -d '=+/' | cut -c1-32)"
export JWT_SECRET="$(openssl rand -base64 48 | tr -d '=+/' | cut -c1-40)"
export DASHBOARD_PASSWORD="$(openssl rand -base64 24 | tr -d '=+/' | cut -c1-24)"As duas chaves de acesso (ANON_KEY e SERVICE_ROLE_KEY) são cartões assinados pelo segredo acima. Gere os dois com o Python que já vem no Ubuntu — não precisa instalar o Node só para isso:
cat > /tmp/mkjw.py <<'PY'
import base64, hmac, hashlib, json, time, os, sys
def b64(data):
return base64.urlsafe_b64encode(data).rstrip(b"=")
secret = os.environ["JWT_SECRET"]
now = int(time.time())
payload = {"role": sys.argv[1], "iss": "supabase", "iat": now, "exp": now + 315360000}
header = b64(json.dumps({"alg": "HS256", "typ": "JWT"}, separators=(",", ":")).encode())
body = b64(json.dumps(payload, separators=(",", ":")).encode())
sig = b64(hmac.new(secret.encode(), header + b"." + body, hashlib.sha256).digest())
print((header + b"." + body + b"." + sig).decode())
PY
echo "ANON_KEY=$(python3 /tmp/mkjw.py anon)"
echo "SERVICE_ROLE_KEY=$(python3 /tmp/mkjw.py service_role)"Guarde os cinco valores gerados em local seguro (gerenciador de senhas). Não envie foto deles aqui nem em lugar nenhum. A SERVICE_ROLE_KEY é a chave mestra: ela só pode existir dentro do servidor.
6. Criar o arquivo de configuração do Supabase
O jeito mais seguro é deixar o próprio servidor gerar as chaves e gravá-las no arquivo, sem você digitar nada. O bloco abaixo cria o .env, preenche as seis linhas de acesso e guarda uma cópia das chaves em /root/supabase-senhas.txt, visível só para o root.
cd "$DB_DIR" || { echo "FALTA: pasta do Supabase nao existe (rode o bloco 4)"; exit 1; }
test -f .env.example || { echo "FALTA: .env.example nao existe (rode o bloco 4)"; exit 1; }
cp .env.example .env && chmod 600 .env
export POSTGRES_PASSWORD="$(openssl rand -base64 32 | tr -d '=+/' | cut -c1-32)"
export JWT_SECRET="$(openssl rand -base64 48 | tr -d '=+/' | cut -c1-40)"
export DASHBOARD_PASSWORD="$(openssl rand -base64 24 | tr -d '=+/' | cut -c1-24)"
cat > /tmp/mkjw.py <<'PY'
import base64, hmac, hashlib, json, time, os, sys
def b64(data): return base64.urlsafe_b64encode(data).rstrip(b"=")
secret = os.environ["JWT_SECRET"]
now = int(time.time())
payload = {"role": sys.argv[1], "iss": "supabase", "iat": now, "exp": now + 315360000}
header = b64(json.dumps({"alg": "HS256", "typ": "JWT"}, separators=(",", ":")).encode())
body = b64(json.dumps(payload, separators=(",", ":")).encode())
sig = b64(hmac.new(secret.encode(), header + b"." + body, hashlib.sha256).digest())
print((header + b"." + body + b"." + sig).decode())
PY
export ANON_KEY="$(python3 /tmp/mkjw.py anon)"
export SERVICE_ROLE_KEY="$(python3 /tmp/mkjw.py service_role)"
python3 - <<'PY'
import os, re
keys = ["POSTGRES_PASSWORD","JWT_SECRET","ANON_KEY","SERVICE_ROLE_KEY","DASHBOARD_PASSWORD"]
vals = {k: os.environ[k] for k in keys}
vals["DASHBOARD_USERNAME"] = "administrador"
text = open(".env").read()
def rep(m):
k = m.group(1)
return f"{k}={vals[k]}" if k in vals else m.group(0)
text = re.sub(r"(?m)^([A-Z0-9_]+)=.*$", rep, text)
open(".env","w").write(text)
with open("/root/supabase-senhas.txt","w") as f:
for k,v in vals.items(): f.write(f"{k}={v}\n")
PY
chmod 600 /root/supabase-senhas.txt
echo "=== conferencia (sem os segredos) ==="
grep -nE '^(POSTGRES_PASSWORD|JWT_SECRET|ANON_KEY|SERVICE_ROLE_KEY|DASHBOARD_USERNAME|DASHBOARD_PASSWORD)=' .env | sed -E 's/=(.{6}).*/=\1.../'
ls -l .env /root/supabase-senhas.txtConfira se as seis linhas aparecem preenchidas e se os dois arquivos estão com permissão -rw-------. As chaves ficam em /root/supabase-senhas.txt: abra com cat /root/supabase-senhas.txt quando precisar delas e guarde no seu gerenciador de senhas. Não envie foto desse arquivo.
Se preferir conferir à mão, o mesmo arquivo abre em nano .env. Ajuste estas linhas (as demais podem ficar como vieram):
POSTGRES_PASSWORD=SENHA_DO_BANCO
JWT_SECRET=SEGREDOS_DE_ASSINATURA
ANON_KEY=CARTAO_PUBLICO
SERVICE_ROLE_KEY=CARTAO_MESTRE
DASHBOARD_USERNAME=administrador
DASHBOARD_PASSWORD=SENHA_DO_PAINEL
SITE_URL=https://db.membresia360.com.br
API_EXTERNAL_URL=https://db.membresia360.com.br
SUPABASE_PUBLIC_URL=https://db.membresia360.com.br
SMTP_HOST=SEU_SERVIDOR_DE_EMAIL
SMTP_PORT=587
SMTP_USER=SEU_EMAIL
SMTP_PASSWORD=SENHA_DO_SMTP
SMTP_ADMIN_EMAIL=SEU_EMAILSITE_URL,API_EXTERNAL_URLeSUPABASE_PUBLIC_URL: os três comhttps://e o mesmo subdomínio deDB_DOMAIN.SMTP_*: sem isso, confirmação de cadastro e recuperação de senha não funcionam. Se não tiver um SMTP, use um provedor de envio (Resend, Brevo, SendGrid) e copie os dados de SMTP deles.DASHBOARD_USERNAMEeDASHBOARD_PASSWORD: protegem o painel administrativo do Supabase.- Salve com Ctrl+O, Enter e saia com Ctrl+X. O
chmod 600já deixa o arquivo visível só para o root.
7. Conferir se alguma porta bate com os sites que já existem
O Supabase usa a porta 8000 para o acesso à API e a 5432 para o banco. O aplicativo hoje usa 3000 e a segunda instância usa 3001. Confira o que está em uso:
ss -tlnp | awk '{print $4}' | grep -E ':(3000|3001|5432|6543|8000|8443)$' | sort -u
grep -nE 'ports:|KONG_HTTP_PORT|KONG_HTTPS_PORT|SUPABASE_PORT|STUDIO_PORT' "$DB_DIR/docker-compose.yml" "$DB_DIR/.env"Se aparecer a porta 3000 publicada pelo Supabase, troque a publicação dela em docker-compose.yml para 127.0.0.1:3100:3000 — assim o painel interno não briga com o Membresia 360. O importante é que nada do Supabase fique escutando em 0.0.0.0:3000.
5432, 6543, 8000 e 8443devem ficar fechadas no firewall: todo acesso passa pelo Nginx, em HTTPS, no subdomínio. Confira com ufw status verbose — devem aparecer liberadas apenas OpenSSH, 80 e 443.8. Criar o proxy do Nginx para o Supabase
cat > "/etc/nginx/sites-available/$DB_DOMAIN" <<EOF
server {
listen 80;
server_name $DB_DOMAIN;
client_max_body_size 50M;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 120s;
}
}
EOF
ln -sfn "/etc/nginx/sites-available/$DB_DOMAIN" "/etc/nginx/sites-enabled/$DB_DOMAIN"
nginx -t && systemctl reload nginxO bloco de cabeçalhos de melhoria de conexão (Upgrade e Connection) é obrigatório: sem ele, as atualizações em tempo real do aplicativo param de chegar.
9. Apontar o subdomínio e ativar HTTPS
No painel de DNS do membresia360.com.br, crie um registro A:
| Tipo | Nome | Valor |
|---|---|---|
| A | db | 91.98.120.163 |
Confira se o nome resolve para o IP desta VPS antes de emitir o certificado:
getent ahostsv4 "$DB_DOMAIN"
apt install -y certbot python3-certbot-nginx
certbot --nginx -d "$DB_DOMAIN" --agree-tos -m "$EMAIL_SSL" --redirect
certbot renew --dry-run10. Subir o Supabase e conferir se os serviços estão de pé
cd "$DB_DIR" && docker compose pull && docker compose up -d
docker compose ps
docker compose logs --tail 50 db auth rest storageEm docker compose ps todos os serviços devem estar running (healthy). Se algum aparecer reiniciando sem parar, leia a log dele (docker compose logs auth) antes de mexer em qualquer coisa.
Confira se a API responde pelo endereço público:
curl -I "https://$DB_DOMAIN/rest/v1/" -H "apikey: COLE_A_CHAVE_PUBLICA"
curl -s "https://$DB_DOMAIN/auth/v1/health"11. Colocar os dados lá dentro (duas rotas)
O Supabase novo nasce com o banco vazio: ele não copia sozinho os cadastros do projeto atual. Escolha uma das rotas:
Rota A — começar do zero (mais segura). Aplique as migrações do aplicativo no banco novo. As 22 migrações estão no repositório, em supabase/migrations. Com o Supabase novo no ar, importe cada arquivo .sql na ordem do nome (do mais antigo ao mais recente) pelo painel do Supabase (SQL Editor) ou com:
APP_CLONE="/opt/membresia360-src"
git clone https://github.com/jogosdaunidade/igreja-conectada-360.git "$APP_CLONE"
DB_URL="postgres://postgres:SENHA_DO_BANCO@127.0.0.1:5432/postgres"
for f in $(ls "$APP_CLONE/supabase/migrations" | sort); do
echo "== aplicando $f"
psql "$DB_URL" -v ON_ERROR_STOP=1 -f "$APP_CLONE/supabase/migrations/$f"
doneDepois de aplicar as migrações, a primeira conta criada pelo aplicativo vira administradora automaticamente (é o gatilho já existente). Aí é recadastrar membros e células — nada do que está hoje no banco atual é aproveitado.
Rota B — copiar os dados atuais. Requer exportar o banco de origem e restaurar no novo, com o aplicativo fora do ar durante a cópia. A cópia precisa incluir login e arquivos, que ficam em partes separadas do banco, e precisa ser conferida item por item antes de apontar o aplicativo para o novo endereço. Não considere essa rota concluída só porque o comando terminou sem erro: é obrigatório comparar a quantidade de membros, células e contas de acesso nos dois lados.
# 1. Aviso: pare o app antes de copiar
pm2 stop "$APP_NAME"
# 2. Cópia de segurança do banco de origem (use o pooler de sessão, nunca o de transação)
pg_dump "postgres://postgres.ID_DO_PROJETO:SENHA_AQUI@HOST_DO_POOLER:5432/postgres" \
--format=custom --file=/root/supabase-origem.dump
# 3. Restauração no Supabase auto-hospedado
pg_restore --no-owner --dbname="postgres://postgres:SENHA_DO_BANCO@127.0.0.1:5432/postgres" \
/root/supabase-origem.dump
# 4. Conferir os dois lados antes de reabrir o site
psql "postgres://postgres:SENHA_DO_BANCO@127.0.0.1:5432/postgres" -c \
"select (select count(*) from public.profiles) as contas,
(select count(*) from public.cells) as celulas,
(select count(*) from public.cell_members) as vinculos,
(select count(*) from public.cell_meetings) as reunioes"
pm2 start "$APP_NAME".dump em destino externo criptografado: ele contém todos os dados pessoais.12. Reapontar o Membresia 360 para o Supabase novo
Só depois de confirmar que o novo Supabase está de pé e com os dados certos. Edite o arquivo de chaves do aplicativo e gere o novo build:
cd "$APP_DIR" && chmod 600 .env.local && nano .env.localCole o modelo com os valores do Supabase novo (as chaves são as geradas no bloco 5):
VITE_SUPABASE_URL="https://db.membresia360.com.br"
VITE_SUPABASE_PROJECT_ID="db.membresia360.com.br"
VITE_SUPABASE_ANON_KEY="CARTAO_PUBLICO"
VITE_SUPABASE_PUBLISHABLE_KEY="CARTAO_PUBLICO"
SUPABASE_URL="https://db.membresia360.com.br"
SUPABASE_PUBLISHABLE_KEY="CARTAO_PUBLICO"
SUPABASE_SERVICE_ROLE_KEY="CARTAO_MESTRE"
VITE_PUBLIC_SITE_URL="https://membresia360.com.br"O endereço novo também precisa ser aceito no Supabase: em Authentication → URL Configuration, inclua https://membresia360.com.br/** nas Redirect URLs e defina a Site URL como https://membresia360.com.br. Depois, gere o build novo e reinicie:
cd "$APP_DIR" && sudo bash public/scripts/deploy.sh "$APP_DIR" "$APP_NAME" "$APP_PORT"Aperte Ctrl+F5 no navegador e entre na sua conta. Se o login falhar, a causa mais comum é chave pública errado ou endereço não liberado nas Redirect URLs — não é problema do aplicativo.
13. Backup e responsabilidade (obrigatório)
Com o banco dentro da VPS, o backup deixa de ser automático do Supabase e passa a ser seu. Configure a rotina de proteção desta VPS — cópia criptografada enviada para fora, verificação por download e retenção de 7 dias — no Passo 36 do guia de instalação. O painel Status da VPS mostra se a cópia externa foi verificada nas últimas 26 horas.
cd /var/www/membresia360 && sudo bash public/scripts/setup-protection.shAtualizações do Supabase auto-hospedado são sua responsabilidade: cd "$DB_DIR" && git -C /opt/supabase-src pull && cp -r /opt/supabase-src/docker/.env.example .env.example && docker compose pull && docker compose up -d. Antes de qualquer atualização, faça uma cópia manual do banco e guarde fora da VPS.
VPS de 4 GB + Supabase na nuvem — publicar com segurança
Neste cenário, somente o Membresia 360 roda na VPS. O banco, os logins e os arquivos continuam no Supabase na nuvem. Os recursos informados — 2 vCPUs, 3,7 GiB de RAM total (3,3 GiB disponíveis) e 34 GB de disco livres — atendem à instalação do aplicativo; acompanhe o consumo conforme o uso crescer. Não instale o Docker nem execute o Passo 23 para esta configuração.
1. Entrar e definir os exemplos substituíveis
No seu computador, entre na VPS escolhida; o IP abaixo é apenas um exemplo:
ssh root@203.0.113.10Dentro da conexão SSH, substitua IP, domínio e e-mail; mantenha os outros valores se estiver instalando este aplicativo:
IP_VPS="203.0.113.10"
DOMINIO="app.suaigreja.com.br"
EMAIL_SSL="admin@suaigreja.com.br"
APP_DIR="/var/www/membresia360"
APP_NAME="membresia360"
APP_PORT="3000"
REPO_URL="https://github.com/jogosdaunidade/igreja-conectada-360.git"Use uma porta livre. Mantenha a mesma conexão; se sair, repita os parâmetros. Após baixar o projeto, trabalhe dentro de /var/www/membresia360 (ou da pasta que escolheu), não em /root.
2. Preparar Ubuntu e uma reserva de memória
apt update && apt upgrade -y && apt install -y curl ca-certificates git nginx ufw build-essential nano
curl -fsSL https://deb.nodesource.com/setup_22.x -o /tmp/m360-node22.sh && bash /tmp/m360-node22.sh && apt install -y nodejs
npm install -g pm2
systemctl enable --now nginx
node -v && npm -v && free -h && df -h /Use Ubuntu 24.04 LTS e Node 22.12 ou mais recente da série 22. A máquina informada não tem swap. O bloco abaixo cria uma reserva de 2 GB somente se não houver swap ativa nem arquivo nesse caminho:
if [ -z "$(swapon --show --noheadings)" ] && [ ! -e /swapfile ]; then
fallocate -l 2G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile && {
grep -qE '^/swapfile[[:space:]]' /etc/fstab || printf '/swapfile none swap sw 0 0
' >> /etc/fstab
}
else
echo "Swap ou /swapfile já existe; confira antes de alterar."
fi
free -h
swapon --showA swap ajuda nos picos da preparação do aplicativo, mas não substitui RAM. Não execute várias atualizações simultâneas nesta VPS de 4 GB.
3. Manter o Supabase na nuvem
- Para manter os cadastros atuais, use o projeto
dyjeylwbxbcvpolnwutd. Não precisa exportar, restaurar ou reinstalar o banco. - Usar esse projeto em outro endereço significa compartilhar os mesmos dados: alterações feitas em qualquer instalação afetam o mesmo banco.
- Para outra igreja com dados independentes, crie um projeto separado e aplique as migrações do aplicativo antes de abrir cadastros. Não basta criar um banco vazio.
- No painel Supabase, confira as chaves nas configurações de API. Mantenha as políticas de acesso por usuário (RLS) e as permissões existentes; nunca desative a proteção para resolver um erro.
- Confira os limites de banco, arquivos, tráfego e disponibilidade do plano contratado. O plano gratuito não garante capacidade ilimitada nem backups adequados para toda operação.
4. Baixar o aplicativo e proteger as chaves
Somente em uma instalação nova, com a pasta ainda inexistente:
mkdir -p /var/www
git clone "$REPO_URL" "$APP_DIR" && cd "$APP_DIR"Se o GitHub pedir senha, use um token válido no campo de senha, nunca na URL do repositório. Não envie o token aqui. Para atualizações sem digitar credenciais, prefira uma chave SSH de implantação com acesso somente de leitura (Passo 7).
cd "$APP_DIR" && (umask 077; touch .env.local) && chmod 600 .env.local && nano .env.localModelo para o banco atual: substitua as chaves e o domínio, mantendo todos os valores entre aspas. Se escolher outro projeto, substitua também o identificador e as duas URLs do Supabase.
VITE_SUPABASE_URL="https://dyjeylwbxbcvpolnwutd.supabase.co"
VITE_SUPABASE_PROJECT_ID="dyjeylwbxbcvpolnwutd"
VITE_SUPABASE_ANON_KEY="CHAVE_PUBLICA_DO_PROJETO"
VITE_SUPABASE_PUBLISHABLE_KEY="CHAVE_PUBLICA_DO_PROJETO"
SUPABASE_URL="https://dyjeylwbxbcvpolnwutd.supabase.co"
SUPABASE_PUBLISHABLE_KEY="CHAVE_PUBLICA_DO_PROJETO"
SUPABASE_SERVICE_ROLE_KEY="CHAVE_SECRETA_DO_PROJETO"
VITE_PUBLIC_SITE_URL="https://app.suaigreja.com.br"
HOST="127.0.0.1"
NITRO_HOST="127.0.0.1"
PORT="3000"VITE_. A chave secreta/service_role fica somente em SUPABASE_SERVICE_ROLE_KEY no servidor. Não publique .env ou .env.local no GitHub, não envie fotos deles e não execute comandos que mostrem seus valores. Salve com Ctrl+O, Enter e saia com Ctrl+X. Não coloque NODE_ENV=production neste arquivo antes de instalar as dependências: a preparação também precisa das dependências de desenvolvimento.5. Preparar e iniciar o site apenas na rede interna
cd "$APP_DIR" && npm install --include=dev --no-audit --no-fund && (
set -a
[ ! -f .env ] || . ./.env
. ./.env.local
set +a
export PORT="$APP_PORT" HOST="127.0.0.1" NITRO_HOST="127.0.0.1" NODE_ENV="production"
NITRO_PRESET=node-server npm run build && pm2 start .output/server/index.mjs --name "$APP_NAME" --update-env
) && pm2 save
pm2 startup systemd -u root --hp /root && pm2 save
curl -I "http://127.0.0.1:$APP_PORT"
ss -ltnp "sport = :$APP_PORT"O endereço de escuta deve ser 127.0.0.1, não 0.0.0.0 nem [::]. Se o processo já existe, não inicie uma cópia: use o bloco 10. O PM2 manterá o processo após reiniciar a VPS; isso não comprova que login e dados estejam funcionando.
6. Configurar Nginx sem servir arquivos privados
O Nginx encaminha o acesso ao aplicativo. Não configure a pasta do projeto como uma pasta pública de arquivos.
cat > "/etc/nginx/sites-available/$APP_NAME" <<EOF
server {
listen 80;
server_name $DOMINIO;
server_tokens off;
client_max_body_size 25M;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location ~ /\.(?!well-known/) { deny all; }
location / {
proxy_pass http://127.0.0.1:$APP_PORT;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
}
}
EOF
ln -sfn "/etc/nginx/sites-available/$APP_NAME" "/etc/nginx/sites-enabled/$APP_NAME" && nginx -t && systemctl reload nginxSomente para uma instalação nova. Não sobrescreva a configuração HTTPS de um site que já está no ar.
7. Firewall, domínio e HTTPS
Antes de ativar o firewall, libere a porta SSH real e mantenha esta sessão aberta. Se não usa a porta 22, troque a primeira regra pela sua porta SSH. Confira também o firewall do provedor e remova regras antigas que exponham a porta do aplicativo.
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw enable
ufw status verbosePara esta instalação, apenas SSH, 80 e 443 precisam de acesso externo. Não abra 3000, 5432 ou 6543: o Supabase está na nuvem.
No DNS, crie um registro A para o domínio escolhido apontando ao IP da VPS. Exemplo: app.suaigreja.com.br → 203.0.113.10. Remova registros conflitantes, inclusive AAAA incorreto. Confira o IP antes de emitir o certificado:
getent ahostsv4 "$DOMINIO"
printf 'IP esperado: %s
' "$IP_VPS"
apt install -y certbot python3-certbot-nginx
certbot --nginx -d "$DOMINIO" --agree-tos -m "$EMAIL_SSL" --redirect
certbot renew --dry-run
curl -I "https://$DOMINIO"Se estiver trocando o servidor do domínio atual, teste primeiro com outro endereço, mantenha a VPS antiga funcionando e só depois altere o DNS. Não exclua o servidor anterior antes da validação.
8. Liberar o endereço e validar o acesso real
No Supabase → Authentication → URL Configuration, adicione o endereço HTTPS às Redirect URLs, por exemplo https://app.suaigreja.com.br/**. Em um projeto compartilhado, preserve a Site URL e os endereços já usados; altere a Site URL somente quando o novo domínio for o principal definitivo.
- Confira as configurações de cadastro, confirmação de e-mail, SMTP e recuperação de senha no Supabase; teste a chegada dos e-mails. Não presuma que o envio esteja configurado.
- Se usar Google ou outros serviços, confira também os endereços autorizados por eles.
- Abra o site com HTTPS, pressione Ctrl+F5, entre com a conta correta e confira membros, células e relatórios.
- Com um registro de teste autorizado, confira gravação e leitura após recarregar. Em banco compartilhado, não apague dados reais para testar.
- Verifique que uma conta de líder não acessa as páginas e ações administrativas. Não considere a publicação segura somente porque o site abre.
cd "$APP_DIR"
pm2 list
pm2 logs "$APP_NAME" --lines 50 --nostream
curl -I "https://$DOMINIO/.env.local"
curl -I "https://$DOMINIO/.git/config"Os dois caminhos privados devem responder 403 ou 404, nunca expor arquivos. Não compartilhe logs sem antes ocultar informações pessoais ou chaves.
9. Backups e alertas antes de considerar concluído
Estar na nuvem não substitui uma estratégia de recuperação. Confira no Supabase quais backups e opções de recuperação seu plano oferece. Para a proteção preparada no Membresia 360, siga o Passo 36 — backups e alertas:
cd /var/www/membresia360 && sudo bash public/scripts/setup-protection.shO instalador exige a conexão PostgreSQL da nuvem (pooler de sessão, não de transação), destino externo criptografado e canais de alerta. Só considere ativo após confirmar uma cópia externa verificada e testar os alertas. Faça um ensaio de restauração em banco isolado; nunca teste restaurando sobre o banco de produção.
A rotina protege o esquema público com membros e células; contas de acesso (Auth) e arquivos (Storage) precisam de proteção separada. Mantenha credenciais e cópias fora das pastas públicas. Confira Status da VPS com uma conta administradora; “Sem confirmação” não significa que existe backup. A leitura real depende também do token de operação configurado no servidor, sem prefixo VITE_.
10. Atualizações seguras e acompanhamento
No servidor atual 91.98.120.163, execute dentro de /var/www/membresia360. Mantenha as chaves da nuvem existentes e confirme HOST e NITRO_HOST como 127.0.0.1 no arquivo protegido. Atualize em horário de baixo uso, após verificar o backup:
cd /var/www/membresia360 && sudo bash public/scripts/deploy.sh /var/www/membresia360 membresia360 3000Se escolheu outra pasta, nome ou porta na instalação nova, use os seus parâmetros:
cd "$APP_DIR" && sudo bash public/scripts/deploy.sh "$APP_DIR" "$APP_NAME" "$APP_PORT"
pm2 list
free -h
df -h /
curl -I "https://$DOMINIO"Depois, pressione Ctrl+F5 e repita o teste de login e leitura dos dados. Em caso de erro, pare e investigue os logs: não apague o banco nem force alterações no Git para tentar resolver. Guarde a versão anterior para recuperação e não automatize atualizações sem validar as mudanças. Mantenha Ubuntu, Node e dependências atualizados e use autenticação em dois fatores nas contas GitHub e Supabase.
Resumo rápido (do zero ao ar)
ssh root@IP_DA_VPS
apt update && apt upgrade -y && apt install -y git nginx ufw
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt install -y nodejs
npm i -g pm2
cd /var/www && git clone https://github.com/USUARIO/REPO.git MEUAPP && cd MEUAPP
nano .env
npm install && npm run build
PORT=PORTA pm2 start ".output/server/index.mjs" --name MEUAPP && pm2 save && pm2 startup
nano /etc/nginx/sites-available/MEUAPP
ln -s /etc/nginx/sites-available/MEUAPP /etc/nginx/sites-enabled/ && nginx -t && systemctl reload nginx
certbot --nginx -d MEUDOMINIO.COM.BR --agree-tos -m SEU@EMAIL.COM --redirect
ufw allow 'Nginx Full' && ufw allow OpenSSH && ufw --force enable