Deploy to a Linux server
Prepare DNS, SSH, Docker, firewall boundaries, and automatic HTTPS for a Nautilo VPS.
This is the persistent self-hosted path: one public DNS name, one SSH-reachable Linux machine, and one Docker Compose lifecycle owned by your circle. A VPS from any provider can work; Nautilo does not have a provider-specific VPS driver.
Install the current signed stable CLI through the Administrator
quickstart. Before
creating infrastructure, confirm nautilo profile add --help lists the remote
Compose, SSH, domain, and HTTPS options used below, and nautilo deploy --help
lists the selected owner-mode options. Update the signed CLI if they are
missing.
Decide the public names first
The generated proxy has two HTTPS sites:
circle.example.orgroutes to the Nautilo Server; andauth.circle.example.orgroutes to Logto.
Replace both synthetic names with names in a public DNS zone you control.
Create A records for both names when the VPS has a reachable IPv4 address.
Create AAAA records only when IPv6 reaches that same VPS; remove stale AAAA
records rather than leaving some clients to fail over IPv6.
Wait until public recursive resolvers return the intended address for both names. Caddy's default HTTP-01 and TLS-ALPN-01 ACME challenges follow public DNS and reach ports 80 or 443. Nautilo and Caddy do not change your DNS zone.
dig +short A circle.example.org
dig +short AAAA circle.example.org
dig +short A auth.circle.example.org
dig +short AAAA auth.circle.example.orgDo not continue while a returned address points to another host.
Prepare the host before creating a profile
Use a maintained Linux distribution supported by Docker Engine. Follow Docker's official installation steps for the host rather than an unofficial convenience script. The target needs:
- Docker Engine and the Docker Compose plugin running;
- a dedicated, noninteractive SSH key already authorized for an
operatoraccount; - permission for that account to use Docker; and
- an owner-writable deployment directory outside any web root.
On the host, an administrator can create that directory once:
sudo install -d -o operator -g operator -m 700 /srv/nautiloThe SSH account plus Docker access is infrastructure-root-equivalent authority. Nautilo application roles cannot revoke it. Restrict, rotate, and remove that access at the host and VPS-provider layers.
Establish the firewall boundary
Allow inbound traffic as follows before deployment:
| Port | Source | Purpose |
|---|---|---|
| SSH port | Named administrator networks only | Nautilo lifecycle operations |
| TCP 80 | Public Internet | HTTP redirect and ACME HTTP-01 validation |
| TCP 443 | Public Internet | Nautilo HTTPS and ACME TLS-ALPN-01 validation |
Block every other inbound port from the Internet. In particular, do not expose the generated direct Server or Logto ports, database ports, or Logto administrator port. The public path is through Caddy on 80/443.
Docker warns that published container ports can bypass rules managed only by
ufw or firewalld. Enforce the public allowlist at the VPS provider or
upstream firewall, and make any host rules Docker-aware. Recheck the effective
policy after Docker starts; a configured host firewall alone is not proof that
a published port is private.
Keep outbound DNS and HTTPS available for image pulls and certificate issuance. Do not add Compose ACME environment flags or install a separate certificate-renewal job.
Pin the SSH host identity
Obtain the expected host-key fingerprint through a trusted provider channel. Then collect the presented key for comparison:
export NAUTILO_SSH_HOST='circle.example.org'
export NAUTILO_SSH_IDENTITY_FILE="$HOME/.ssh/nautilo_vps_operator"
export NAUTILO_SSH_KNOWN_HOSTS_FILE="$HOME/.ssh/nautilo_vps_known_hosts"
ssh-keyscan -t ed25519 -H "$NAUTILO_SSH_HOST" > /tmp/nautilo-vps.known-hosts
ssh-keygen -lf /tmp/nautilo-vps.known-hostsStop if the fingerprints differ. Do not replace an existing entry until a key change is independently verified. After a match, install the entry and test the exact noninteractive path Nautilo will use:
install -m 600 /tmp/nautilo-vps.known-hosts "$NAUTILO_SSH_KNOWN_HOSTS_FILE"
rm /tmp/nautilo-vps.known-hosts
ssh -o BatchMode=yes -o IdentitiesOnly=yes -l operator \
-i "$NAUTILO_SSH_IDENTITY_FILE" \
-o StrictHostKeyChecking=yes \
-o UserKnownHostsFile="$NAUTILO_SSH_KNOWN_HOSTS_FILE" \
"$NAUTILO_SSH_HOST" \
'docker info && docker compose version && test -w /srv/nautilo'Resolve Docker permission, missing Compose, or directory ownership now. Do not fall back to an interactive password or disable host-key checking.
Create the remote HTTPS profile
nautilo profile add circle-server \
--transport=remote \
--lifecycle=compose \
--instance-id=circle \
--ssh-host="$NAUTILO_SSH_HOST" \
--ssh-user=operator \
--ssh-identity-file="$NAUTILO_SSH_IDENTITY_FILE" \
--ssh-known-hosts-file="$NAUTILO_SSH_KNOWN_HOSTS_FILE" \
--remote-path=/srv/nautilo \
--domain=circle.example.org \
--base-url=https://circle.example.org \
--https=letsencrypt \
[email protected] \
--yesThe --domain value is the Server hostname. Nautilo derives the Logto host as
auth.<domain> and generates both Caddy sites. The base URL must use the exact
Server hostname over HTTPS.
For repeated pre-production testing, create a separate disposable profile with
--acme-staging so failed experiments do not consume production CA limits.
Staging certificates are not browser-trusted and do not prove production HTTPS.
Deploy with an explicit owner mode
For an interactive first-owner browser claim:
nautilo deploy \
--profile circle-server \
--owner-mode claim \
--finish guideThe claim URL is valid only for the exact HTTPS deployment origin. Check the
hostname and certificate before entering the one-time capability. Require
owner-bound, save the recovery codes, and sign in again as the new owner.
Alternatively, a compatible signed CLI can create the first owner from a
protected file. Follow the exact automated Compose setup
steps,
including explicit --owner-mode config, --owner-config, and
--owner-result. Do not switch between owner modes after an interrupted run.
Bare deploy verifies the signed stable Server release and selects its
immutable runtime digest before preparing deployment state. To deploy a
specific release, add
--image ghcr.io/agentsea/nautilo-runtime@sha256:<64-lowercase-hex-digest>.
Profiles do not store image selection.
Verify routing, TLS, and the Server
nautilo status --profile circle-server
curl --fail --silent --show-error https://circle.example.org/health
curl --fail --silent --show-error \
https://auth.circle.example.org/oidc/.well-known/openid-configuration
openssl s_client -connect circle.example.org:443 \
-servername circle.example.org </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
openssl s_client -connect auth.circle.example.org:443 \
-servername auth.circle.example.org </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -datesThe two curl commands perform normal hostname and trust validation; never add
--insecure. The openssl output is for certificate identity and date
inspection, not chain validation. From a normal client network, open the exact
HTTPS Server URL with no certificate warning, confirm the expected Server
identity, sign in as the owner, and complete one useful model-backed action. A
running container or a successful TLS handshake alone is not Server
acceptance.
Certificate issuance troubleshooting
If HTTPS is not ready, preserve the profile and deployment state. Do not keep recreating the Server or repeatedly request production certificates.
-
Query public DNS for both the Server and
authnames. Correct wrong or stale A/AAAA records and allow for their configured TTLs. -
Confirm the provider/upstream firewall allows public TCP 80 and 443 to this host, and that no NAT rule sends either port elsewhere.
-
Check whether another host process already owns those ports:
ssh -o BatchMode=yes -l operator \ -i "$NAUTILO_SSH_IDENTITY_FILE" \ -o StrictHostKeyChecking=yes \ -o UserKnownHostsFile="$NAUTILO_SSH_KNOWN_HOSTS_FILE" \ "$NAUTILO_SSH_HOST" 'ss -ltnp | grep -E "(:80|:443)[[:space:]]"' -
Inspect Caddy's managed logs without printing environment files:
nautilo logs --profile circle-server --service caddy -
After fixing DNS, firewall, or a port conflict, leave Caddy running. It retries certificate management with backoff. Re-run the TLS checks above; do not edit the generated Caddyfile or add an ad hoc ACME client.
Renewal and certificate custody
Caddy obtains and renews managed certificates automatically. The generated Compose overlay persists Caddy's data and configuration; keep those volumes writable and durable, keep both DNS names directed at the host, and leave the public validation ports reachable. Deleting Caddy storage forces new issuance and can trigger CA limits.
Check the certificate dates and names periodically with the openssl commands
above and alert before expiry. If renewal fails, use the same DNS, port,
storage, and Caddy-log checks. The domain registration, DNS account, VPS,
SSH/Docker authority, and Caddy certificate storage remain operator custody;
Nautilo application ownership is not a substitute for any of them.
Day-two release changes use nautilo upgrade --profile circle-server, which
wraps backup, runtime acceptance, and automatic rollback around replacement.
Do not use deploy as the ordinary upgrade command.