Implantação
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.
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 healthPrimeiro 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.
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/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 redenelcota_edge. O Postgres também participa de uma redeegresspró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 umpg_dumppor projeto para permitir a saída.
Domínio próprio ou subdomínio
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.localhostO 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:
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 upComo 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); usamsystemctl,journalctlerunuser -u postgresem vez dedocker 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.jsonmantém a propriedade do diretório do host entre comandos interrompidos. Somente umremoveconcluí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.envdo 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/mainna porta 5432. Um17/mainconfigurado 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 dePGCLUSTER,PGHOST,PGHOSTADDR,PGPORTou 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. upgradetroca 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.removefaz o backup final, depois exclui a unidade e o cluster Postgres com seus dados (pg_dropcluster).down --volumesfunciona 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.
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
- Verifica Docker e o plugin compose (um usuário fora do grupo
dockerrecebe a orientação de usar sudo ouusermod -aG docker $USER), portas 80/443 livres (em uso = erro, com orientação para encontrar o processo;--skip-checkscontinua mesmo assim), RAM e DNS do domínio. - Gera os segredos do host: credenciais de administrador (senha exibida uma vez;
host.envguarda apenas o hash argon2id) e o segredo de login único. - 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 quehost.envtem um bucket, então S3 pode ser adicionado depois. - Máquina: com
--firewall, configuraufw(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 (
--reinstalla 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-runmostracurrent → targetpara cada projeto sem alterar nada.
nelcota upgrade --all --dry-run
nelcota -p shop upgrade --version 0.4.0 # published target; already-current projects are skippedDesenvolvimento local
nelcota dev # Postgres 17 in a container (127.0.0.1:54322) + server on :8000Os 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
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.