Pular para o conteúdo
Nelcota
EN : Read in English

Implantação

10 min de leitura Editar no GitHub

Provisione PostgreSQL, o app Rust e Caddy para HTTPS em uma VPS. Você precisa de Linux suportado, acesso administrativo, DNS e portas 80/443 abertas. Escolha explicitamente uma versão publicada do CLI/imagem; recursos exclusivos da fonte exigem publicação e validação próprias. Node não é necessário.

shell
curl -fsSL https://raw.githubusercontent.com/Nelcota/Nelcota/main/scripts/install.sh | sh     # binary (with checksum) + Docker if missing
mkdir -p /opt/nelcota && cd /opt/nelcota
nelcota init api.yourdomain.com                 # creates the host and the first project
nelcota up                                      # starts projects + Caddy, waits for the healthcheck
nelcota doctor                                  # optional: checks DNS, ports, backups and health

Primeiro crie um registro DNS A para o domínio apontando para o IP da VPS. O Caddy emite o certificado automaticamente na primeira requisição. Após iniciar tudo, up consulta https://<domain>/health por até um minuto; se a URL pública não responder, lista o que verificar (DNS, firewall, logs do Caddy) e termina com código diferente de zero. Hosts locais (--local) pulam essa verificação.

Sem curl | sh: baixe nelcota-<architecture> e nelcota-<architecture>.sha256 da página de versões, execute sha256sum -c e copie para /usr/local/bin. O script faz exatamente isso.

O conector nativo usa NoTls em rede privada; uma URL com sslmode não é um conector TLS. Mantenha portas do banco privadas. Segredos de produção devem ser estáveis/privados; não use credenciais de desenvolvimento ou tokens de serviço em aplicativos.

Host e projetos

Uma VPS pode hospedar vários projetos, cada um isolado: seu próprio Postgres, usuários, chaves JWT, painel e backups. Um único projeto é simplesmente um host com um projeto.

text
Internet ──80/443──▶ Caddy (shared)
                        ├── api.shop.com ──▶ app-shop ──▶ shop's Postgres   (its own internal network)
                        └── blog.example.com ──▶ app-blog ──▶ blog's Postgres
text
/opt/nelcota/                   (the host folder; -C <folder> or NELCOTA_ROOT)
├── nelcota-host.json           registry: login mode, base domain, projects (no secrets)
├── host.env                    host secrets (0600): admin, SSO secret, S3
├── shared/projects.json        public list (name + URL), mounted into the apps for the switcher
├── caddy/                      shared Caddy; the Caddyfile is generated from the registry
├── archive/                    final backups of removed projects
└── projects/<name>/            docker-compose.yml, .env (0600), migrations/, backups/
  • Apenas o Caddy publica portas. O Postgres de cada projeto fica em sua própria rede internal: true; os aplicativos se comunicam com o Caddy pela rede nelcota_edge. O Postgres também participa de uma rede egress própria, apenas de saída, para arquivar WAL no S3 quando PITR está ativado.
  • Um Postgres por projeto, sem compartilhamento: os papéis do Postgres (anon, authenticated, authenticator...) valem para todo o servidor, e servidores separados mantêm o isolamento físico e um pg_dump por projeto para permitir a saída.

Domínio próprio ou subdomínio

shell
nelcota init api.shop.com                                # own domain → project "shop"
nelcota init api.shop.com --project sales                # explicit name
nelcota init --project blog --base-domain example.com    # subdomain: blog.example.com
nelcota init --project panel                             # uses the base domain already saved on the host
nelcota init --local --project shop                      # local: https://shop.localhost

O nome do projeto vem do domínio (api.shop.com → shop) ou de --project. Ele aparece no seletor de projetos do painel e em -p nos comandos.

Os argumentos são validados antes do provisionamento. Uma falha de pacote, filesystem ou serviço durante o provisionamento ainda pode deixar arquivos/serviços; inspecione a etapa indicada antes de repetir.

Opções de init

Opção O que faz
--project <name> nome do projeto (padrão: derivado do domínio)
--base-domain <domain> salva o domínio-base do host: --project shop se torna shop.<base>
--local host local sem domínio público (https://<project>.localhost, certificado interno); apenas na criação do host
--panel-login shared|per-project modo de login do painel na criação do host (padrão shared)
--email <email> e-mail do administrador (padrão admin@<domain>)
-y, --yes não faz perguntas (apenas padrões e opções)
--image <image> imagem do aplicativo (padrão ghcr.io/nelcota/nelcota-server ou NELCOTA_IMAGE)
--version <tag> tag da imagem (padrão: versão da CLI)
--profile 1gb|2gb|4gb|8gb perfil do Postgres (padrão: conforme a RAM dividida entre os projetos)
--s3-endpoint, --s3-bucket, --s3-access-key, --s3-secret-key, --s3-region bucket de backup, escrito em host.env (as quatro opções ou nenhuma; região padrão us-east-1). Sem elas, um init interativo pergunta, lendo a chave secreta sem exibi-la
--runtime docker|systemd como os projetos executam (apenas na criação do host; veja abaixo)
--firewall configura ufw (SSH, 80 e 443)
--skip-checks pula verificações de Docker, portas, RAM e DNS

Comandos

Com um único projeto, -p é opcional. Com vários, comandos que atuam sobre um projeto exigem -p <name>.

Comando O que faz
nelcota projects lista projetos, estado e versão
nelcota up inicia todos os projetos e o Caddy (-p para apenas um), depois verifica o HTTPS público
nelcota status saúde do aplicativo e do Postgres, idade do último backup, versão e espaço livre em disco
nelcota doctor verifica Docker, Caddy/portas 80 e 443, espaço livre, DNS, cron de backup, um backup com menos de 26 h por projeto, S3 e saúde dos serviços; termina com código diferente de zero em qualquer problema
nelcota -p shop logs -f [app|postgres] / logs caddy logs do projeto / logs do proxy compartilhado
nelcota -p shop down / down --all para (--volumes EXCLUI os dados após confirmação, --yes a dispensa; apenas Docker)
nelcota -p shop migrate aplica projects/shop/migrations/V<n>__<name>.sql
nelcota -p shop types -o database.ts / types --lang rust -o database.rs tipos TypeScript (padrão) ou Rust do esquema
nelcota -p shop token service-role JWT de serviço (ignora RLS)
nelcota -p shop backup / backup --all --keep 7 dump em backups/, enviado para S3 quando host.env tem um bucket (Backup e restauração)
nelcota -p shop restore <file> restaura um dump, após um backup de segurança do estado atual
nelcota -p shop pitr enable / status / restore --time ... recuperação para um ponto no tempo com WAL arquivado em S3 (Backup e restauração)
nelcota -p shop upgrade / upgrade --all atualiza com backup e reversão automática (--dry-run mostra atual → alvo; retrocessos exigem --allow-downgrade)
nelcota -p shop remove backup final em archive/ (iniciando Postgres se necessário; --no-backup o dispensa), remove contêineres, dados e o site Caddy
nelcota panel-login shared / per-project login do painel: único ou por projeto
nelcota admin-password nova senha do painel (-p com login por projeto)

Em um host, migrate, types e token executam dentro do contêiner app do projeto, que é quem consegue alcançar o Postgres. Fora de um host, usam NELCOTA_DATABASE_URL.

Sem Docker (systemd)

Em uma VPS pequena que terá apenas um projeto, o Nelcota pode dispensar o Docker:

shell
curl -fsSL https://raw.githubusercontent.com/Nelcota/Nelcota/main/scripts/install.sh | NELCOTA_SKIP_DOCKER=1 sh
mkdir -p /opt/nelcota && cd /opt/nelcota
nelcota init api.yourdomain.com --runtime systemd
nelcota up

Como root no Debian ou Ubuntu, init instala Postgres 17 e pgBackRest do repositório apt do projeto PostgreSQL (PGDG) e Caddy de seu próprio repositório, depois:

O quê Onde
Perfil do Postgres (por RAM) /etc/postgresql/17/main/conf.d/nelcota.conf
O aplicativo nelcota-<name>.service, executando /usr/local/lib/nelcota/nelcota serve em 127.0.0.1:8000 com o .env do projeto
Caddy o serviço caddy, /etc/caddy/Caddyfile gerado a partir do registro
Configurações PITR /etc/pgbackrest/pgbackrest.conf (com pitr enable)
  • Instale AWS CLI v2 no host antes de usar backups externos de dumps/arquivos. O provisionamento nativo instala Postgres, pgBackRest e Caddy, não AWS CLI.
  • Todos os comandos funcionam da mesma forma (up, down, status, logs, migrate, backup, restore, pitr, upgrade, remove); usam systemctl, journalctl e runuser -u postgres em vez de docker compose.
  • Um projeto por máquina: um segundo init é recusado, inclusive a partir de outro diretório e com --skip-checks. Uma trava de máquina serializa init/remoção; /var/lib/nelcota-native/owner.json mantém a propriedade do diretório do host entre comandos interrompidos. Somente um remove concluído com sucesso a libera. Não exclua esse registro para contornar uma instalação interrompida: inspecione e recupere o host original por SSH. Instalações anteriores sem registro só são reconhecidas quando sua unit existente aponta sem ambiguidade para o caminho absoluto do .env do host selecionado; estado conflitante ou incompleto exige recuperação explícita. Para vários projetos, use Docker.
  • O PostgreSQL nativo usa o cluster local 17/main na porta 5432. Um 17/main configurado de outra forma, ou outro cluster PostgreSQL reservando a porta 5432, é rejeitado antes de instalar pacotes ou escrever a nova configuração do host ou projeto, inclusive com --skip-checks. Comandos nativos de banco de dados não selecionam outras portas. Os clientes administrativos nativos selecionam explicitamente esse cluster e o socket local; configurações herdadas de PGCLUSTER, PGHOST, PGHOSTADDR, PGPORT ou de serviços não podem redirecionar um backup, uma restauração ou uma atualização de credencial para outro banco de dados.
  • A unidade do aplicativo executa como um usuário temporário (DynamicUser), sem acesso de escrita ao sistema (ProtectSystem=strict) e sem capabilities.
  • upgrade troca o binário executado pela unidade pelo que você está executando (portanto, instale primeiro a nova versão: NELCOTA_VERSION=x.y.z install.sh), mantém o anterior e, se o aplicativo não ficar saudável, coloca o anterior de volta e restaura o backup anterior à atualização.
  • remove faz o backup final, depois exclui a unidade e o cluster Postgres com seus dados (pg_dropcluster).
  • down --volumes funciona apenas com Docker.

Login do painel

  • Único (padrão): um e-mail e uma senha para todos os painéis do host. Ao entrar em um painel e trocar de projeto pelo seletor, a senha não é pedida novamente.
  • Por projeto: cada painel tem suas próprias credenciais; o seletor apenas leva ao outro painel, que pede seu próprio login.
shell
nelcota panel-login per-project     # generates and shows one password per project
nelcota panel-login shared          # back to single login (host password)

Detalhes de segurança em Painel.

O que o primeiro init faz

  1. Verifica Docker e o plugin compose (um usuário fora do grupo docker recebe a orientação de usar sudo ou usermod -aG docker $USER), portas 80/443 livres (em uso = erro, com orientação para encontrar o processo; --skip-checks continua mesmo assim), RAM e DNS do domínio.
  2. Gera os segredos do host: credenciais de administrador (senha exibida uma vez; host.env guarda apenas o hash argon2id) e o segredo de login único.
  3. Backup: pergunta o destino compatível com S3 (ou use --s3-*) e, como root, instala a tarefa cron diária para todos os projetos (03:00, mantém 7). A tarefa faz upload sempre que host.env tem um bucket, então S3 pode ser adicionado depois.
  4. Máquina: com --firewall, configura ufw (SSH, 80 e 443).

Cada init posterior cria apenas o projeto: seus próprios segredos (Postgres, authenticator, chave Ed25519), um perfil Postgres conforme a RAM dividida entre projetos e o site Caddy (recarregado sem parar os outros).

A capacidade de projetos depende de consultas, dados, hashing, uploads e conjuntos de trabalho do PostgreSQL. Escolha um perfil, meça sua carga e monitore a margem disponível. O guia de benchmark registra fixtures específicas; não garante projetos por GB.

Migrações

SQL simples em projects/<name>/migrations/, com nomes V1__create_tables.sql, V2__indexes.sql... Cada arquivo executa em uma transação. O acompanhamento fica em nelcota.user_migrations (versão, nome, data, checksum): se um arquivo já aplicado for editado, migrate recusa. Após aplicar, a API recarrega o catálogo automaticamente.

Alterações feitas no painel

Tabelas, colunas e políticas criadas no painel vão direto para o banco do projeto, mas não existem em outro ambiente (staging, outra máquina, banco novo) até se tornarem um arquivo. O painel registra cada alteração e lista as pendentes em Migrações; "Gerar migração" baixa V<n>__<name>.sql com elas e já a registra como aplicada neste banco, com o mesmo checksum calculado por migrate. Coloque o arquivo em migrations/ e faça commit: nada executa novamente aqui, e migrate o aplica nos outros ambientes.

O número escolhido fica além de tudo no banco e na pasta (o contêiner lê ./migrations, montado em /migrations), então um arquivo ainda não aplicado nunca é ultrapassado. Fora do contêiner, indique a pasta com NELCOTA_MIGRATIONS_DIR. O que você executa no editor SQL não é registrado: copie esse SQL manualmente para uma migração.

Atualização

nelcota -p shop upgrade disponibiliza a imagem-alvo antes de alterar o projeto em execução. Depois coloca o site Caddy desse projeto em manutenção (HTTP 503 com Retry-After), para o aplicativo para concluir requisições existentes e captura o banco e os arquivos de disco juntos. Só então altera NELCOTA_VERSION em .env, inicia a nova versão e aguarda sua verificação de saúde.

Se a nova versão não ficar saudável, a CLI restaura a versão anterior, o banco e o snapshot de disco correspondente antes de reabrir o tráfego. Se a criação do backup falhar, reinicia a versão anterior sem alterações. Uma recuperação que falhe ou seja interrompida mantém o bloqueio de manutenção fechado; após corrigir a causa, nelcota -p shop up verifica a saúde e o reabre. Os outros projetos continuam atendendo tráfego; upgrade --all atualiza um por vez, continua quando um projeto falha (ele foi revertido) e lista as falhas ao final.

Sem --version, o alvo é a versão da própria CLI. A CLI a compara primeiro com NELCOTA_VERSION do projeto:

  • mesma versão: nada a fazer (--reinstall a reimplanta mesmo assim, por exemplo, um binário recompilado em um host systemd);
  • alvo mais antigo: recusado, pois o aplicativo antigo não conhece as migrações mais recentes. Quando a própria CLI é a antiga, reinstale-a (curl -fsSL https://raw.githubusercontent.com/Nelcota/Nelcota/main/scripts/install.sh | sh); para voltar intencionalmente, passe --allow-downgrade;
  • --dry-run mostra current → target para cada projeto sem alterar nada.
shell
nelcota upgrade --all --dry-run
nelcota -p shop upgrade --version 0.4.0  # published target; already-current projects are skipped

Desenvolvimento local

shell
nelcota dev        # Postgres 17 in a container (127.0.0.1:54322) + server on :8000

Os segredos de desenvolvimento (senhas do painel e do Postgres, chave JWT) ficam em .nelcota/dev.env, legível apenas por você; a mensagem de inicialização diz onde estão em vez de exibi-los. nelcota token service-role --days 30 emite um token service_role para esse ambiente.

--db-port <port> move o Postgres de desenvolvimento para outra porta local. A nova porta é salva em .nelcota/dev.env; recebe seu próprio contêiner e dados, e o contêiner antigo permanece como estava.

Para testar a implantação completa localmente (vários projetos, Caddy, HTTPS com certificado interno): nelcota init --local --project shop e abra https://shop.localhost/admin/.

Teste de aceitação

shell
ACCEPT_DOMAIN=api.example.com ./scripts/acceptance.sh   # real VM
./scripts/acceptance.sh --local                          # local simulation

Última execução local (2026-10-06, Windows 11 + Docker Desktop, imagens em cache): dois projetos com HTTPS em 28 s. Também passaram: migrate (idempotente), cadastro e RLS pelo Caddy, isolamento entre projetos (o JWT de um projeto não é válido no outro; as tabelas de um projeto não existem no outro), login único entre painéis com token de uso único, troca para login por projeto e volta, types, backup → escrita → restauração, reversão automática de atualização sem afetar o outro projeto e remoção de projeto com backup em archive/.

Essa execução local datada é evidência histórica. Ela não valida uma VPS atual, DNS/TLS público, runtime systemd nativo ou atualizações controladas. Versões publicadas já existem; execute a validação no host/versão alvo antes de confiar no comportamento em produção.

Gerenciamento opcional do host

O controlador ainda não lançado pode provisionar e gerenciar projetos Docker isolados após cadastro explícito por SSH. Aprove uma imagem oficial, o escopo de domínios e os endereços da VPS antes de habilitar a criação; examine as revisões das operações e mantenha acesso de recuperação. Consulte docs/host-management.md para layouts aceitos e aceitação em produção.