Deployment Architecture
How nano deploys across tiers, from single-node Docker Compose to multi-pool Kubernetes with ClickHouse Cloud
Deployment Architecture
nano scales from a single VPS to a multi-pool Kubernetes cluster depending on your tier. This page covers what gets deployed at each level and how the services fit together.
Architecture Overview
Every nano deployment runs the same core services. What changes across tiers is how many replicas run, how storage is managed, and whether databases run in-cluster or as managed services.
Tiers
| Tier | GB/Day | EPS | Replicas | HA | Cloud Providers |
|---|---|---|---|---|---|
| Hobby | 2 | 30 | 1 | No | Hetzner |
| Startup | 5 | 75 | 1 | No | Hetzner |
| Growth | 10 | 155 | 1 | No | Hetzner |
| Team | 25 | 385 | 2 | Yes | Civo, Vultr, GCP, AWS |
| Business | 50 | 770 | 2 | Yes | Civo, Vultr, GCP, AWS |
| Pro | 100 | 1,550 | 3 | Yes | GCP, AWS |
| Enterprise | Custom | Unlimited | 4 | Yes | GCP, AWS |
Hobby & Startup: Docker Compose
The smallest tiers run as a single-node Docker Compose deployment on a Hetzner VPS. All services share one machine, with no Kubernetes overhead.
| Hobby | Startup | |
|---|---|---|
| Machine | 2 CPU / 8 GB | 4 CPU / 16 GB |
| Storage | 50 GB | 50 GB |
| ClickHouse | Single node, no replication | Single node, no replication |
| PostgreSQL | Single instance | Single instance |
| Replicas | 1 of everything | 1 of everything |
| Retention | 365+ days, FIFO | 365+ days, FIFO |
Growth: Single-Pool Kubernetes
Growth moves to Kubernetes but keeps everything in one node pool. ClickHouse runs via the ClickHouse Operator, PostgreSQL via CloudNativePG.
| Aspect | Growth |
|---|---|
| Nodes | 1 x 4 CPU / 8 GB |
| ClickHouse | Operator-managed, 1 shard x 2 replicas |
| PostgreSQL | CNPG, 1 instance |
| Keeper | 3-node quorum (lightweight) |
| App replicas | 1 (API, Search, Web, Vector) |
| Storage | 100 GB hot |
| Retention | 365+ days, FIFO |
| Providers | Hetzner |
Team & Business: HA Kubernetes
Team and Business add high availability: multiple replicas, database replication, and failover.
| Team | Business | |
|---|---|---|
| Nodes | 3 x 4 CPU / 8 GB | 3 x 4 CPU / 8 GB |
| App replicas | 2 | 2 |
| ClickHouse | 1 shard x 2 replicas | 1 shard x 2 replicas |
| PostgreSQL | 2 instances (primary + replica) | 2 instances (primary + replica) |
| Storage | 120 GB hot | 300 GB hot |
| Object storage | Wasabi S3 | Wasabi S3 |
| Retention | 365 days | 365 days |
| Providers | Civo, Vultr, GCP, AWS | Civo, Vultr, GCP, AWS |
Pro: Multi-Pool Kubernetes
Pro separates workloads into dedicated node pools: stateless app services on spot instances, ClickHouse on dedicated on-demand nodes, and Vector on its own spot pool.
| Pool | Nodes | Machine | Scheduling | Purpose |
|---|---|---|---|---|
| App | 3 | 2 CPU / 8 GB | Spot | API, Search, Frontend, Prometheus |
| ClickHouse | 4 | 4 CPU / 16 GB | On-demand | ClickHouse (2 shards x 2 replicas) |
| Vector | 3 | 2 CPU / 8 GB | Spot | Log ingestion and parsing |
| Aspect | Pro |
|---|---|
| App replicas | 3 (API, Search, Web) |
| ClickHouse | 2 shards x 2 replicas (4 pods) |
| PostgreSQL | 3 instances (primary + 2 replicas) |
| Storage | 1 TB hot (250 GB per CH pod) |
| Object storage | Wasabi S3 |
| Retention | 365 days |
| Providers | GCP, AWS |
Enterprise: Managed Databases
Enterprise removes databases from the Kubernetes cluster entirely. ClickHouse runs on ClickHouse Cloud, PostgreSQL on Cloud SQL (GCP) or RDS (AWS). The K8s cluster only runs application services and Vector.
| Volume | App Pool | Vector Pool |
|---|---|---|
| Up to 100 GB/day | 3 x 4 CPU / 16 GB | 3 x 4 CPU / 16 GB |
| 100 GB - 1 TB/day | 3 x 4 CPU / 16 GB | 4 x 8 CPU / 32 GB |
| 1-10 TB/day | 3 x 8 CPU / 32 GB | 6 x 16 CPU / 64 GB |
| 10-50+ TB/day | 6 x 8 CPU / 32 GB | 12+ x 16 CPU / 64 GB |
- ClickHouse Cloud handles storage, replication, and scaling, with no local disk management
- PrivateLink connects the K8s cluster to ClickHouse Cloud for network isolation
- 4 replicas of all application services (API, Search, Frontend)
- Providers: GCP, AWS only
Services
API Service
All replicas serve HTTP requests simultaneously behind the load balancer. A PostgreSQL advisory lock elects one replica as the scheduler leader. Only the leader runs background tasks:
- Detection rule execution (cron-based scheduled queries)
- Signal processing (materialized view alerts)
- Enrichment auto-sync (IPinfo, threat feeds)
- Auto-tuning (baseline collection, threshold detection)
If the leader pod dies, another replica acquires the lock within ~15 seconds. HTTP serving is uninterrupted.
Search Service (Active/Active)
The Search service is stateless: all replicas serve queries simultaneously behind the load balancer. Query state, results, and search history are stored in ClickHouse and PostgreSQL, so any replica can handle any request.
On HA tiers (Team+), multiple Search replicas run with pod anti-affinity to spread across nodes. If a pod dies, the remaining replicas continue serving traffic with no interruption.
Search connects to a dedicated clickhouse-search service that targets a specific ClickHouse replica. This isolates read queries from Vector's ingestion writes, preventing heavy searches from competing with data ingestion on the same node.
Vector (Log Ingestion)
Vector accepts logs on multiple protocols:
| Port | Protocol | Auth | Use Case |
|---|---|---|---|
| 8080 | HTTP | Bearer token | Primary ingestion: applications, webhooks, log shippers |
| 8088 | HEC | HEC token | Splunk HTTP Event Collector compatible forwarders |
| 6000 | Vector native | mTLS (client certs) | On-prem Vector aggregators |
Vector is stateless and spot-tolerant. On HA tiers, multiple replicas run with pod anti-affinity to spread across nodes.
ClickHouse
ClickHouse handles all log storage and query execution. Configuration varies by tier:
| Tier | Topology | Keeper | Storage |
|---|---|---|---|
| Hobby/Startup | Single node | None | Local disk |
| Growth | 1 shard x 2 replicas | 3-node quorum | Local disk |
| Team/Business | 1 shard x 2 replicas | 3-node quorum | Local + Wasabi S3 |
| Pro | 2 shards x 2 replicas | 3-node quorum | Local + Wasabi S3 |
| Enterprise | ClickHouse Cloud | Managed | Managed |
On Team+ tiers, older data is automatically moved to object storage (Wasabi S3 or your own S3/GCS bucket) while remaining fully searchable. There's no meaningful difference in query speed.
ClickHouse compresses data at roughly 8-10x, so 100 GB/day of raw logs uses approximately 10-12 GB/day of storage.
PostgreSQL
PostgreSQL stores metadata: rules, alerts, users, dashboards, credentials, and search history. Deployed via the CloudNativePG operator with streaming replication and automatic failover.
| Tier | Instances | HA |
|---|---|---|
| Hobby/Startup | 1 | No |
| Growth | 1 | No |
| Team/Business | 2 (primary + replica) | Yes |
| Pro | 3 (primary + 2 replicas) | Yes |
| Enterprise | Cloud SQL / RDS | Managed |
Deployment Modes
| Mode | Description |
|---|---|
| Managed | nano provisions and manages the infrastructure on supported cloud providers |
| BYOC (Bring Your Own Cloud) | nano deploys into your cloud account using your credentials |
Both modes use the same architecture. Managed deployments handle provisioning, upgrades, and monitoring automatically. BYOC gives you full control over the cloud account and network configuration.
AWS IAM
For a BYOC deployment on AWS, nano needs permission to create and tear down the EKS cluster, VPC, and load balancers in your account. There are two ways to grant it.
| Option | What nano stores | Revoke by |
|---|---|---|
| IAM role (recommended) | A role ARN — no credentials | Deleting the role |
| Access keys | An encrypted access key pair | Deleting the IAM user |
The role option is preferred: nano never holds long-lived credentials for your
account, every action appears in your CloudTrail under the session name
nano-provisioning, and deleting the role revokes access immediately without
having to rotate anything.
How the role works
nano provisions from its own AWS account, so your role trusts that account and is gated on an External ID unique to your organisation. The External ID is what stops anyone else — including another nano customer who somehow learned your role ARN — from asking nano to use your role.
Both values are shown on the credential form in the nano console when you pick IAM role as the credential type: nano's AWS account ID, and your organisation's External ID.
Option 1: CloudFormation
Download the template and create the stack:
curl -O https://nano.rs/docs/nano-provisioning-role.yaml
aws cloudformation deploy \
--template-file nano-provisioning-role.yaml \
--stack-name nano-provisioning \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides \
NanoAccountId=<NANO_ACCOUNT_ID> \
ExternalId=<YOUR_EXTERNAL_ID>Then read the role ARN back and paste it into the credential form:
aws cloudformation describe-stacks \
--stack-name nano-provisioning \
--query 'Stacks[0].Outputs[?OutputKey==`RoleArn`].OutputValue' \
--output textYou can also create the stack through the CloudFormation console by uploading the same file.
Option 2: AWS CLI
If you don't use CloudFormation — or manage infrastructure in Terraform, or have service control policies restricting stack creation — the role is just three calls. Create the trust policy:
cat > nano-trust-policy.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::<NANO_ACCOUNT_ID>:root" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "sts:ExternalId": "<YOUR_EXTERNAL_ID>" }
}
}]
}
EOFCreate the role, then attach permissions:
aws iam create-role \
--role-name NanoProvisioning \
--assume-role-policy-document file://nano-trust-policy.json \
--max-session-duration 43200
aws iam put-role-policy \
--role-name NanoProvisioning \
--policy-name nano-provisioning \
--policy-document file://nano-provisioning-policy.jsonExtract nano-provisioning-policy.json from the Policies block of the
CloudFormation template above so the two paths grant identical permissions.
arn:aws:iam::ACCOUNT:root in a trust policy means "that AWS account", not
the account's root user. It delegates the decision to that account's own IAM
policies — it does not grant access to anyone at nano without the External ID.
Session duration
Set MaxSessionDuration to 12 hours (43200 seconds). A large EKS cluster takes
longer to build than AWS's one-hour default session, and a session that expires
mid-provision leaves half-built infrastructure behind that still bills. If the
role won't allow a 12-hour session nano falls back to one hour, which is enough
for smaller tiers but not guaranteed for a full multi-pool cluster.
Permissions granted
| Service | Why |
|---|---|
eks:* | Create and manage the cluster and its managed node groups |
ec2:* | VPC, subnets, NAT gateways, security groups, worker instances |
elasticloadbalancing:* | Load balancers fronting the deployment |
iam (scoped to nano-* roles) | Cluster and node roles, plus the OIDC provider for IRSA |
logs | Cluster logging |
sts:GetCallerIdentity | Confirms the role was assumed successfully |
The IAM permissions are scoped by resource name to roles nano creates, so the role cannot modify your other IAM principals.
Revoking access
Delete the CloudFormation stack, or the role if you created it by hand. nano loses access immediately — there are no credentials left behind to rotate. Do this only after destroying any deployment in the account, since teardown needs the same permissions as provisioning.
Monitoring
Prometheus scrapes all services at 10-15 second intervals:
| Endpoint | Metrics |
|---|---|
api:3000/metrics | API latency, request counts, scheduler health |
search:3002/metrics | Query performance, admission control |
vector:9598/metrics | Ingestion throughput, buffer sizes, parse errors |
postgres-exporter:9187 | Database health, replication lag |
Next Steps
- Set Up Your First Feed: onboard your first log source
- On-Premise Collection: deploy Vector agents and aggregators
- Search & Query: learn the query language