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. On larger tiers a companion clickhouse-search-preferred service targets a specific ClickHouse replica, so read queries can be kept off the replica Vector writes to and heavy searches do not compete with ingestion on the same node. That preference is never a requirement: clickhouse-search itself always targets every replica, so if the preferred replica's availability zone is lost, search continues against the surviving replica rather than failing.
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.
Data Durability and Retention
nano protects log data with replication and retention rather than point-in-time backup. This is the standard model for log platforms — Splunk, Microsoft Sentinel, Google SecOps, Sumo Logic and Datadog all work this way — and it follows from what log data is: append-only, high-volume, deliberately expired on a schedule, and still present at the sources that produced it. Metadata is small and transactional, so it is backed up conventionally.
The two are covered separately below, because their guarantees genuinely differ.
Log data (ClickHouse)
Every part is stored more than once on every tier that offers HA:
| Tier | Copies of each part | Spread across |
|---|---|---|
| Hobby, Startup | 1 | Single node — no replication |
| Growth, Team, Business | 2 (1 shard x 2 replicas) | Separate nodes |
| Pro | 2 per shard (2 shards x 2 replicas) | Separate nodes |
| Enterprise on AWS | 2 per shard | Separate availability zones |
| Enterprise on ClickHouse Cloud | Managed by ClickHouse Cloud | Managed |
On Enterprise AWS the two replicas of a shard are placed in different AZs by a required topology constraint, and ClickHouse Keeper runs a 3-node quorum spread one per AZ, so losing an availability zone costs neither data nor quorum. On other providers and tiers replicas are spread one-per-node rather than one-per-AZ.
What replication protects against: losing a node, a disk, a volume, or — on Enterprise AWS — an entire availability zone. A replica that dies is rebuilt automatically from its peer, and queries continue against the surviving one throughout.
What replication does not protect against: anything that acts on the data
logically. A DROP TABLE, an ALTER DELETE, or a bad schema migration is a replicated
operation — it is applied to every copy, because applying operations to every copy is
precisely what replication is. Both replicas are the same dataset, not two independent
ones. There is no snapshot of a previous state to roll back to.
Tiering is not a backup. Data older than the hot window is moved to object storage, not copied to it — the storage policy relocates parts between volumes and the cold tier is part of the live dataset, which is exactly why it stays queryable. A logical deletion removes the data from whichever tier currently holds it.
Retention is by tier — 365 days on Team and above, 365+ days FIFO on the single-node
tiers — and is enforced by a DELETE TTL on the table. Expiry is the only mechanism that
routinely removes data, and it applies regardless of which volume a part has moved to.
Recovery from logical destruction is re-ingestion from your own sources. This is the industry's real recovery path for log data and the one nano is designed around: the devices, cloud accounts, and applications that generated the events are the system of record, and most of them retain their own copy for some period. Where an event's only copy is the one in nano, treat the retention window as the exposure and keep an independent archive of anything you cannot afford to re-collect — the on-premise collection tier can fan out to a second destination for exactly this purpose.
nano does not offer point-in-time restore of log data, and no automated backup of the
ClickHouse dataset is taken. Where a contract requires one, scheduled ClickHouse BACKUP
to a dedicated bucket is available as a paid add-on scoped per engagement — talk to us
before signing.
Cold-tier immutability
The object-storage tier is versioned. Overwritten and deleted objects remain recoverable for 30 days, and a lifecycle rule expires them after that so storage stays bounded.
This is a deliberate defence against a compromised deployment: the credential the
ClickHouse pods hold can delete objects, but on a versioned bucket a delete writes a
marker and leaves the data intact behind it. Permanently destroying a version requires
s3:DeleteObjectVersion, and disabling versioning requires s3:PutBucketVersioning —
neither of which is granted to anything running inside the deployment. An attacker who
takes over the cluster can therefore hide aged data, reversibly, but not destroy it.
The hot tier has no equivalent protection; this covers data that has already aged out.
Metadata (PostgreSQL)
PostgreSQL holds rules, alerts, users, dashboards, credentials, and search history — not log events. It is backed up conventionally, because it is small enough for that to be cheap:
| Deployment | Backup | Retention |
|---|---|---|
| Kubernetes tiers (in-cluster PostgreSQL) | Daily pg_dump, 04:00 UTC, uploaded to nano-managed object storage | 7 rolling daily copies |
| Enterprise (managed RDS) | AWS automated backups, 04:00–05:00 window | 7 days |
| Hobby, Startup (single-VM Compose) | None | — |
Enterprise RDS instances are Multi-AZ. On nano-managed deployments, teardown takes a
final snapshot rather than discarding the database, as a safety net against an
accidental destroy. On BYOC the default is the opposite: destroying a deployment
destroys its database outright, with no snapshot left in your account, so that one
teardown leaves you with a genuinely clean account. Ask us to pass
retainFinalSnapshot if you would rather keep the net — it is your account, and the
snapshot is yours to keep and to pay storage on.
Restoring a PostgreSQL backup is an operator-assisted action; open a support request rather than expecting a self-serve button.
BYOC: your account, your backup policy
On a customer-hosted (BYOC) deployment the data never leaves your cloud account. The ClickHouse volumes, the cold-storage bucket, and the managed PostgreSQL instance are all resources you own, in a region you chose, under your own organisation's policies.
That means every durability control your organisation already runs applies to nano without nano's involvement:
- EBS snapshots of the ClickHouse data volumes, on whatever schedule and retention your backup policy specifies — including AWS Backup plans and cross-region or cross-account snapshot copies.
- Your own bucket policy on the cold tier, including S3 Object Lock if your compliance regime calls for WORM storage, replication to a second region, or a longer version-retention window than nano's default 30 days.
- RDS snapshots on your own schedule, in addition to the automated 7-day retention.
- Your key management, audit logging, and access reviews — every action nano takes
appears in your CloudTrail under the session name
nano-provisioning.
If your security questionnaire asks who can restore your data, on BYOC the answer is that you can, on your own terms, without depending on nano's roadmap. Recovering log data from a volume snapshot is crash-consistent rather than transactionally clean, so treat it as a bulk-recovery mechanism rather than a precise point-in-time restore — but it is a genuine one, and it is entirely under your control.
AWS IAM
For a BYOC deployment on AWS, nano needs permission to create and tear down the EKS cluster, VPC, load balancers, the managed PostgreSQL database, and the cold-tier storage bucket 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 |
acm (certificate lifecycle) | The TLS certificate the load balancer's HTTPS listener serves, requested for your deployment's hostname and deleted on teardown |
rds (scoped to nano-* databases) | The managed PostgreSQL instance the deployment keeps its application data in, and the subnet group that places it in your private subnets |
s3 (scoped to nano-* buckets) | The cold-tier bucket: create it, turn on versioning and its lifecycle rule, and read/write the archived data in it |
iam (scoped to nano-* roles) | Cluster and node roles, the OIDC provider for IRSA, and the bucket-scoped role your ClickHouse pods assume |
logs | Cluster logging |
sts:GetCallerIdentity | Confirms the role was assumed successfully |
The IAM, RDS, and S3 permissions are scoped by resource name to the nano-*
prefix, so the role can neither modify your other IAM principals, see your other
buckets, nor touch your other databases.
Two read-only exceptions cannot be scoped: rds:DescribeDBInstances and
rds:DescribeDBSubnetGroups act on a collection rather than a named resource, so
IAM has no resource to pin them to. They return metadata — endpoint address,
status, and the subnet ids the database sits in — and cannot read or change the
contents of any database.
The ACM permissions cannot be scoped either: a certificate's ARN does not exist
until acm:RequestCertificate returns it, so IAM has nothing to pin the request
to. The certificate is validated through DNS in nano's own zone — nano needs no
route53 permission in your account — and its private key never leaves AWS.
Teardown deletes the database without leaving a final snapshot in your account. Asking us to destroy a deployment should leave nothing of it behind, and a retained snapshot is a full copy of your platform database — users, detection rules, API keys, tenant configuration — that would otherwise sit there indefinitely and keep costing you storage. Tell us before the teardown if you want a snapshot kept instead; on nano-managed deployments, where the instance lives in our account, one is kept by default as a safety net.
Offboarding also removes the four shared nano-eks-* IAM roles, but only once
your last nano deployment on the account is gone — they are shared across
deployments, so removing them while another is running would break its node
group and CSI driver. If anything still references a role, we leave it in
place and record it on the teardown record rather than forcing the deletion.
Cold storage needs no separate credentials
ClickHouse tiers data older than the hot retention window out to an S3 bucket in your account, and still queries it there. That bucket is covered by the same role — you do not create a bucket, an IAM user, or an access key for it.
At provision time nano creates a nano-prefixed bucket (encrypted with SSE-S3,
all public access blocked, versioned — see
Cold-tier immutability) and an IAM role scoped to just
that bucket. That role
is trust-bound to the Kubernetes ServiceAccount the ClickHouse pods run under,
via the cluster's OIDC provider — the mechanism AWS calls
IRSA.
The pods authenticate with a short-lived web-identity token that EKS rotates, so
no long-lived storage credential is ever created, stored, or transmitted.
Deleting the stack revokes this along with everything else. Tearing down the deployment removes the bucket and the bucket-scoped role.
Onboarding with access keys instead of the role has no OIDC provider to bind to, so cold storage there falls back to a bucket and a scoped key pair that you create and nano stores encrypted.
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