Objetivo: sair do Supabase e rodar um portal de notícias full stack em uma VPS da Hostinger usando Coolify, PostgreSQL, backend Dart Shelf e frontend NgDart 8.
Cenário do projeto:
- Backend: API REST em Dart com Shelf.
- Frontend: NgDart 8 compilado como arquivos estáticos.
- Acesso a dados: biblioteca
eloquent.dart, compatível com PostgreSQL e MySQL. - Banco de origem: Supabase, baseado em PostgreSQL.
- Banco recomendado no destino: PostgreSQL.
- Deploy recomendado: Docker Compose gerenciado pelo Coolify.
Hostinger VPS
├── Coolify
├── PostgreSQL 16
├── Backend Dart Shelf
├── Frontend NgDart 8 estático
├── Proxy/HTTPS do Coolify
└── Backups automáticos
Fluxo recomendado:
Visitante
↓
Cloudflare DNS/CDN
↓
Hostinger VPS
├── Frontend NgDart estático
├── API Dart Shelf
└── PostgreSQL
Para começar, use:
https://seudominio.com.br → frontend
https://api.seudominio.com.br → backend API
Depois, se quiser simplificar CORS, você pode migrar para:
https://seudominio.com.br/api → backend API
Para o portal de notícias, recomendo uma VPS com pelo menos:
2 vCPU
4 GB RAM
80 GB SSD/NVMe
Ubuntu 24.04 LTS
O ideal para trabalhar com folga:
2 vCPU
8 GB RAM
100 GB SSD/NVMe
Ubuntu 24.04 LTS
Motivo: o Coolify, PostgreSQL, builds Docker, backend Dart e frontend estático cabem em 4 GB, mas 8 GB evita travamentos durante build e restauração de banco.
Se a Hostinger oferecer o template pronto, escolha:
Ubuntu 24.04 with Coolify
Esse template já vem com Coolify pré-instalado. Se não aparecer, escolha Ubuntu 24.04 LTS limpo e instale o Coolify manualmente.
No painel DNS do seu domínio, crie:
Tipo Nome Valor
A @ IP_DA_VPS
A www IP_DA_VPS
A api IP_DA_VPS
Exemplo:
seudominio.com.br → IP_DA_VPS
www.seudominio.com.br → IP_DA_VPS
api.seudominio.com.br → IP_DA_VPS
Se usar Cloudflare, deixe inicialmente em DNS only durante a primeira configuração. Depois que HTTPS estiver funcionando, você pode ativar o proxy.
Se você criou a VPS usando o template Ubuntu 24.04 with Coolify, acesse o painel da Hostinger e procure as instruções de acesso ao Coolify.
Normalmente o acesso será algo como:
http://IP_DA_VPS:8000
Crie o usuário administrador no primeiro acesso.
Entre na VPS por SSH:
ssh root@IP_DA_VPSAtualize o servidor:
apt update && apt upgrade -yInstale o Coolify:
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bashDepois acesse:
http://IP_DA_VPS:8000
No painel do Coolify:
Projects
→ New Project
→ portal-noticias
Dentro do projeto, você pode separar os recursos assim:
portal-postgres
portal-api
portal-frontend
Para seu caso, eu recomendo usar Docker Compose único, porque a aplicação é full stack e fica mais fácil versionar tudo no Git.
Como a origem é Supabase, escolha PostgreSQL.
Motivos:
- Supabase já usa PostgreSQL.
- A migração é mais direta.
- Você evita converter tipos, funções e índices.
- PostgreSQL oferece recursos excelentes para portal de notícias:
jsonb,tsvector, índices GIN, views, full-text search e extensões. - Sua biblioteca
eloquent.dartjá funciona com PostgreSQL.
Use MySQL apenas se o projeto ainda não tiver banco no Supabase ou se você tiver um motivo forte de hospedagem/custo. Neste caso, como já existe Supabase, PostgreSQL é o caminho natural.
Você pode criar pelo painel do Coolify ou pelo Docker Compose.
Exemplo de serviço PostgreSQL:
services:
postgres:
image: postgres:16
container_name: portal-postgres
restart: unless-stopped
environment:
POSTGRES_DB: portal
POSTGRES_USER: portal_user
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- portal_postgres_data:/var/lib/postgresql/data
networks:
- portal_net
volumes:
portal_postgres_data:
networks:
portal_net:Importante: não exponha a porta 5432 publicamente. A API deve acessar o banco pela rede interna Docker.
Instale a CLI do Supabase na sua máquina local.
Faça login:
supabase loginEntre na pasta do projeto:
cd caminho/do/projetoVincule o projeto remoto:
supabase link --project-ref SEU_PROJECT_REFGere o dump:
supabase db dump --linked --file supabase_dump.sqlEsse método é melhor do que tentar fazer pg_dump bruto do banco inteiro, porque o Supabase possui schemas internos, roles e permissões próprias.
Se quiser gerar apenas schema público e dados principais, revise a documentação do Supabase e confira se você usa:
auth.users
storage.objects
realtime
edge functions
policies RLS
schemas privados
Se usa apenas tabelas comuns no schema public, a migração tende a ser simples.
Na sua máquina local:
scp supabase_dump.sql root@IP_DA_VPS:/root/supabase_dump.sqlEntre na VPS:
ssh root@IP_DA_VPSConfira se o arquivo chegou:
ls -lh /root/supabase_dump.sqlListe os containers:
docker psProcure o container do PostgreSQL. Exemplo:
portal-postgres
Copie o dump para dentro do container:
docker cp /root/supabase_dump.sql portal-postgres:/tmp/supabase_dump.sqlRestaure:
docker exec -it portal-postgres psql -U portal_user -d portal -f /tmp/supabase_dump.sqlTeste se as tabelas apareceram:
docker exec -it portal-postgres psql -U portal_user -d portal -c "\dt"Se quiser contar registros de uma tabela:
docker exec -it portal-postgres psql -U portal_user -d portal -c "select count(*) from noticias;"Alguns projetos Supabase usam extensões. Antes ou depois da restauração, talvez seja necessário ativar:
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE EXTENSION IF NOT EXISTS citext;Se você usa busca vetorial:
CREATE EXTENSION IF NOT EXISTS vector;Mas vector exige imagem PostgreSQL com pgvector instalado. Para portal de notícias comum, provavelmente não precisa.
Use variáveis de ambiente para o banco:
APP_ENV=production
APP_PORT=8080
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=portal
DB_USERNAME=portal_user
DB_PASSWORD=SENHA_FORTENo Docker Compose, o host do banco será o nome do serviço:
postgres
Exemplo de conexão na aplicação:
postgres://portal_user:SENHA_FORTE@postgres:5432/portal
Ajuste bin/server.dart para o nome real do arquivo principal da sua API.
FROM dart:stable AS build
WORKDIR /app
COPY pubspec.* ./
RUN dart pub get
COPY . .
RUN dart compile exe bin/server.dart -o bin/server
FROM debian:bookworm-slim
WORKDIR /app
COPY --from=build /runtime/ /
COPY --from=build /app/bin/server /app/server
EXPOSE 8080
CMD ["/app/server"]Se a API precisa de arquivos estáticos, migrations ou templates, copie também:
COPY --from=build /app/migrations /app/migrations
COPY --from=build /app/config /app/configO NgDart 8 deve ser compilado para arquivos estáticos.
Exemplo:
webdev build --releaseou:
dart run build_runner build --releaseDepois, sirva o resultado com Nginx.
FROM nginx:alpine
COPY build/web /usr/share/nginx/html
EXPOSE 80Se a pasta final do build for diferente, ajuste o caminho.
Estrutura sugerida do repositório:
portal-noticias/
├── docker-compose.yml
├── .env
├── backend/
│ ├── Dockerfile
│ ├── pubspec.yaml
│ └── bin/server.dart
└── frontend/
├── Dockerfile
├── pubspec.yaml
└── web/
docker-compose.yml:
services:
postgres:
image: postgres:16
container_name: portal-postgres
restart: unless-stopped
environment:
POSTGRES_DB: portal
POSTGRES_USER: portal_user
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- portal_postgres_data:/var/lib/postgresql/data
networks:
- portal_net
api:
build:
context: ./backend
dockerfile: Dockerfile
container_name: portal-api
restart: unless-stopped
environment:
APP_ENV: production
APP_PORT: 8080
DB_CONNECTION: pgsql
DB_HOST: postgres
DB_PORT: 5432
DB_DATABASE: portal
DB_USERNAME: portal_user
DB_PASSWORD: ${POSTGRES_PASSWORD}
depends_on:
- postgres
networks:
- portal_net
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: portal-frontend
restart: unless-stopped
depends_on:
- api
networks:
- portal_net
volumes:
portal_postgres_data:
networks:
portal_net:Arquivo .env:
POSTGRES_PASSWORD=troque_esta_senha_por_uma_senha_muito_forteNo Coolify, cadastre o repositório Git e informe que o deploy usa Docker Compose.
Configuração inicial mais simples:
frontend → seudominio.com.br
api → api.seudominio.com.br
No frontend, configure a URL da API:
API_BASE_URL=https://api.seudominio.com.brSe quiser evitar CORS no futuro, configure proxy para:
https://seudominio.com.br/api
Mas para a primeira migração, subdomínio api é mais simples de debugar.
Se você usa Supabase Storage para imagens das notícias, precisa migrar os arquivos separadamente.
Opções:
Opção A: salvar imagens no disco da VPS
Opção B: usar Cloudflare R2
Opção C: usar MinIO no próprio VPS
Recomendação: Cloudflare R2.
Motivo:
- Imagens crescem rápido.
- Backup de mídia no disco da VPS pode ficar pesado.
- R2 é compatível com API S3.
- Você pode salvar no banco apenas o caminho/URL da imagem.
Fluxo recomendado:
Upload da notícia
↓
Backend Dart
↓
Cloudflare R2
↓
PostgreSQL salva URL/path
Migração básica:
- Baixar arquivos dos buckets do Supabase.
- Subir arquivos para R2 ou outro storage.
- Atualizar URLs no banco.
- Testar imagens antigas no portal novo.
Na VPS:
docker psLogs da API:
docker logs -f portal-apiTeste local da API:
curl http://localhost:8080/healthTeste pelo domínio:
curl https://api.seudominio.com.br/healthTeste o banco:
docker exec -it portal-postgres psql -U portal_user -d portal -c "\dt"Teste contagem de notícias:
docker exec -it portal-postgres psql -U portal_user -d portal -c "select count(*) from noticias;"Checklist funcional:
- Página inicial abre.
- API responde
/health. - Listagem de notícias funciona.
- Cadastro de notícia funciona.
- Edição funciona.
- Upload de imagem funciona.
- URLs públicas das imagens funcionam.
- Login/admin funciona, se existir.
- HTTPS está válido.
- Logs não mostram erro recorrente.
Não desligue o Supabase imediatamente.
Faça assim:
- Suba tudo no VPS.
- Restaure um dump de teste.
- Teste o portal no domínio provisório.
- Coloque o portal antigo em modo manutenção ou pause cadastros.
- Gere o dump final do Supabase.
- Restaure o dump final na VPS.
- Valide contagem de registros.
- Vire o DNS.
- Mantenha o Supabase ativo por alguns dias como fallback.
- Só cancele Supabase depois de confirmar que não há dados faltando.
Configure backup diário do PostgreSQL.
Opções:
Coolify backup integrado
pg_dump manual via cron
backup para S3/R2
snapshot da VPS
Comando manual para backup:
mkdir -p /root/backups
docker exec portal-postgres pg_dump \
-U portal_user \
-d portal \
--format=custom \
--no-owner \
--no-acl \
> /root/backups/portal_$(date +%F).dumpRestaurar backup .dump:
docker exec -i portal-postgres pg_restore \
-U portal_user \
-d portal \
--clean \
--if-exists \
--no-owner \
--no-acl \
< /root/backups/portal_2026-07-30.dumpRetenção sugerida:
Diário: 7 a 14 dias
Semanal: 4 semanas
Mensal: 3 a 6 meses
Atualize o sistema:
apt update && apt upgrade -yConfigure firewall básico:
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enableNão exponha:
PostgreSQL 5432
Docker socket
Painel administrativo sem senha forte
Use senhas fortes e guarde em local seguro:
Senha root/SSH
Senha do Coolify
POSTGRES_PASSWORD
Chaves do storage R2/S3
Antes de migrar, confirme se o projeto usa:
Supabase Auth
Supabase Storage
Supabase Realtime
Edge Functions
RLS policies
Triggers
Functions SQL
Schemas diferentes de public
Extensões PostgreSQL
Se usa apenas tabelas comuns, a migração é simples.
Se usa auth.users, você precisa decidir:
1. Migrar usuários para autenticação própria
2. Continuar usando Supabase Auth temporariamente
3. Criar tabela própria de usuários/admins
Se usa RLS policies, teste bem. Em um backend próprio, normalmente a regra de autorização fica no backend Dart, e não diretamente no PostgreSQL.
Quando for fazer a migração real, envie:
1. Lista de tabelas do Supabase
2. Se usa Supabase Auth
3. Se usa Supabase Storage
4. Se usa RLS
5. Tamanho aproximado do banco
6. Quantidade/tamanho das imagens
7. Estrutura do backend Dart
8. Como o eloquent.dart lê as variáveis de conexão
Com isso, a ordem prática será:
1. Preparar VPS
2. Acessar Coolify
3. Criar PostgreSQL
4. Exportar Supabase
5. Restaurar dump
6. Subir backend Dart
7. Subir frontend NgDart
8. Testar rotas
9. Migrar imagens
10. Virar DNS
- Coolify Docs — Installation: https://coolify.io/docs/get-started/installation
- Coolify Docs — Docker Compose: https://coolify.io/docs/knowledge-base/docker/compose
- Coolify GitHub: https://github.com/coollabsio/coolify
- Supabase Docs — Restore a Platform Project to Self-Hosted: https://supabase.com/docs/guides/self-hosting/restore-from-platform
- Supabase Docs — Database Backups: https://supabase.com/docs/guides/platform/backups
- Hostinger Help Center — Coolify VPS template: https://www.hostinger.com/support/9615197-how-to-use-the-coolify-vps-template-at-hostinger/