Production Install Runbook (Fresh Server) — This runbook is the practical fresh-server process to bring up a brand-new node from scratch. ⬆ índice
Production Install Runbook (Fresh Server)
This runbook is the practical fresh-server process to bring up a brand-new node from scratch.
If the server is already bootstrapped and reachable as externaladmin on the hardened SSH port, do not use this runbook. Use the deploy/update flow referenced from Deployment.
Assumptions
- You have a Debian/Ubuntu VPS with a public IP.
- DNS is under your control.
- You start with provider access: SSH
root@<host>on TCP port22using a temporary password. - Final access will be: SSH
externaladmin@<host>on TCP port36987.
Inputs you must have
IP_OR_DOMAIN: the server IP or DNS name (example:sd1.desarrolloselectronicos.com)- Provider temporary root password (used only for bootstrap): you will be prompted during
remote bootstrap(or exportREMOTE_ROOT_PASSWORD) - Desired admin password (used for sudo during deploy): you will be prompted during
remote bootstrap/remote deploy(or exportREMOTE_EXTERNAL_ADMIN_PASSWORD) - Your SSH public key (recommended):
REMOTE_SSH_PUBLIC_KEY_PATH(or exportSSH_PUBLIC_KEY)
1) What you must do first
Before you run any install commands, do this checklist in order.
DNS
If the domain is delegated to Cloudflare, generate/sync records from the repo:
./infra remote dns cloudflare <host> --zone <domain> --preflight
./infra remote dns cloudflare <host> --zone <domain>
./infra remote dns cloudflare <host> --zone <domain> --apply --wait
For initial account and nameserver setup, see Alta de zona. For the complete workflow and CLI reference, see Cloudflare.
For any other DNS provider, print the hostnames you need to create from your current config:
./infra remote domains
Create A/AAAA records for each hostname (pointing to the server public IP).
Wait until they resolve:
host <any-endpoint>.<domain>
Provider firewall / security group
Allow inbound:
- TCP
22(temporary; bootstrap only) - TCP
36987(final SSH port) - TCP
80,443(HTTP/HTTPS) - any other public ports you intentionally expose (optional)
If this checklist is not green, stop here and fix it first. Otherwise you'll hit noisy, misleading failures later.
2) How to run the process
Configure local env + secrets
These are the canonical sources of truth used by scripts and Ansible:
- Topology/ports (non-secret):
deployment/topology.env(generated from template) - Public endpoints/domains (non-secret):
deployment/remote.env - Service runtime secrets for servers: Ansible Vault (recommended) via
core2_env_secrets(rendered to{{ core2_repo_root }}/.env.secretsduring deploy) - Optional local secrets for development:
.env.secrets(never commit; not synced to servers)
Choose ONE path: Option A OR Option B (do not do both).
Option A (recommended): guided wizard (standard flow). This walks you through the same steps as the manual path (env -> server registration -> bootstrap -> deploy) and then runs basic verification checks.
./infra init remote
If you choose Option A, you can skip the manual steps below.
Option B: run the manual steps below:
Generate and validate locally:
./infra env generate
./infra env check
Important note:
- These env files are used from your local machine to drive Ansible and generate configs.
remote bootstrapdoes not "generate env on the server".remote deploysyncs the repo and seeds missing per-project.envfiles from.env.example/.env.templatewhen needed.
Register the server (inventory)
./infra server add <IP_OR_DOMAIN> --user root --port 22
./infra server list
Bootstrap (one-time hardening)
Bootstrap uses the initial provider access (root@22 + temporary password) to harden the server and enable key-based access.
./infra remote bootstrap --host <IP_OR_DOMAIN>
After bootstrap, you should be able to connect on the hardened port:
ssh -p 36987 externaladmin@<IP_OR_DOMAIN>
Deploy (repeatable)
This repository now uses a staged remote deploy strategy:
core: Traefik + private registrysystem: PostgreSQL + Redis + Zitadel bootstrapapps: application services
Application deploys build locally, push to the private registry, and pull remotely. Normal deploys do not build on the server. Read:
docs/internal/operations/remote-minimal-deploy.md
./infra remote deploy <IP_OR_DOMAIN> core
./infra remote deploy <IP_OR_DOMAIN> system
./infra remote deploy <IP_OR_DOMAIN> apps
./infra remote deploy <IP_OR_DOMAIN> <project>
./infra release <IP_OR_DOMAIN> <project>
Use:
remote deploy core|systemfor routing, registry, database, Redis, Zitadel, and other non-image refreshesrelease <project>for application updates when the project image changed and must be rebuilt/published before deploy- after deploys on 8GB hosts, run the memory validation block in:
docs/internal/operations/remote-minimal-deploy.md
3) How to verify it worked (and lock it down)
Prefer SSH keys (no password) for server access
Before you consider the server "production-ready", ensure you can SSH with keys (no password prompts).
If you provided REMOTE_SSH_PUBLIC_KEY_PATH (or SSH_PUBLIC_KEY), bootstrap should have installed your key. Verify:
ssh -p 36987 -o BatchMode=yes externaladmin@<IP_OR_DOMAIN> "echo ok"
Operational checks
On the server:
ssh -p 36987 externaladmin@<IP_OR_DOMAIN>
sudo ufw status verbose
sudo systemctl is-active docker fail2ban
sudo fail2ban-client status sshd
docker ps
curl -fsS http://127.0.0.1:5000/v2/
From your local machine:
./infra remote deploy <IP_OR_DOMAIN> <project>
./infra remote logs <IP_OR_DOMAIN> <project>
If public endpoints exist, validate:
- HTTPS certs (issuer + expiry)
- app pages return expected content
- only expected ports are open
Registry (expected to be loopback-only on the server):
./infra remote tunnel <IP_OR_DOMAIN> registry
curl -fsS http://127.0.0.1:5000/v2/
Lock down bootstrap access
After everything is stable:
- close inbound TCP
22at the provider firewall (keep only36987) - keep a break-glass procedure using provider console access
Final post-install checks
After the first successful deploy, review Backups and confirm the scheduler/snapshot process.
If the host is public, confirm public HTTPS is served by Traefik and Let's Encrypt certificates were issued. For local/Vagrant/private-IP targets, the fallback mode uses local HTTP or self-signed routing depending on deployment/remote.env.
Common install failures (quick fixes)
-
Bootstrap fails to connect as root
- provider firewall blocks port 22, or wrong password in
.env.secrets - verify:
nc -zv <host> 22
- provider firewall blocks port 22, or wrong password in
-
Deploy fails on public TLS
- DNS not pointing to the server yet, or port 80/443 blocked
- temporary workaround for private/local targets:
REMOTE_TLS_MODE=http ./infra remote deploy <host> core
-
Deploy sync is slow or fails
- check disk space and network on the server
- verify:
df -handjournalctl -u ssh -n 200 --no-pager