Skip to content

ClickHouse analytical column store

Template

ClickHouse analytical column store

This validated OpenTofu template composes Compute, Network, and Block Storage into a self-hosted analytical column store you run on infrastructure you control.

What this template does#

Provisions a single instance running ClickHouse, a fast analytical column store (a self-hosted alternative to the query layer of BigQuery or Snowflake):

  • Compute instance that runs ClickHouse in Docker, sized for moderate analytical workloads (2 vCPU and 8 GiB RAM)
  • Private network, subnet, router, port, and security group; a floating IP for controlled access
  • A block volume mounted at /var/lib/docker, so ClickHouse table data lives on a volume you can grow rather than on the boot disk
  • cloud-init installs Docker Engine and starts ClickHouse from a compose file on first boot

ClickHouse stores and queries large analytical datasets on a VM you own. Pair it with self-managed PostgreSQL for transactional workloads, or use it as the warehouse layer in a lakehouse stack.

No credential ships with this template. cloud-init generates a database password on first boot and writes it to /opt/clickhouse/.bootstrap-user on the instance.

Parameters#

ParameterDescriptionDefault
key_nameSSH keypair name (must already exist)No default
flavor_nameInstance size (ClickHouse on 2 vCPU / 8 GiB)m2a.large
image_nameOperating system imageUbuntu-24.04
app_nameDisplay name prefix for resourcesclickhouse
volume_sizeBlock volume size in GiB, mounted at /var/lib/docker50
external_networkExternal network for floating IP allocationPublicStatic
private_cidrCIDR for the private subnet10.49.0.0/24
client_allowed_cidrCIDR allowed to reach ClickHouse on ports 8123 and 900010.49.0.0/24

Client access and security#

ClickHouse listens on port 8123 (HTTP) and 9000 (native TCP). The security group restricts both ports to client_allowed_cidr, which defaults to the private network only, so the raw database stays off the public internet. Reach ClickHouse one of three ways:

  • Put a reverse proxy (Caddy or Nginx) in front of the HTTP interface and serve queries over HTTPS on 443. Point the domain's DNS A record at the floating IP. This is the recommended path for routine access.
  • Tunnel over SSH: ssh -L 8123:localhost:8123 -L 9000:localhost:9000 user@FLOATING_IP, then connect to localhost.
  • Set client_allowed_cidr to YOUR_IP/32 to reach ports 8123 and 9000 directly from one address.

Ports 80 and 443 stay open for the reverse proxy you put in front; they carry no traffic until you add one.

When to use this pattern#

Run an analytical column store for aggregations, time-series rollups, and large scan queries on a VM you operate. ClickHouse suits event analytics, log pipelines, and BI backends that outgrow Postgres-as-warehouse. For BI dashboards on top of the warehouse, see Metabase or Apache Superset in the data tooling catalog.

Estimated cost#

Monthly cost estimate

Pricing calculator ↗

Sized as a custom package on dedicated vCPU.

Starting template$68.20/mo

Monthly total for the required template above. Use the configurator below to add optional pieces and see the total update.

What each resource is for

ClickHouse host

m2a.large · 2 dedicated vCPU, 8 GiB RAM, 0.5 Gbps

Runs ClickHouse in Docker (analytical column store), with table data on an attached volume.

ClickHouse analytical workloads run on 2 vCPU and 8 GiB RAM. Size up for heavier query concurrency or larger in-memory working sets.

$66.00/mo

Compute shown per role at custom-package rates ($29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM). The headline above is the billed total: the cheaper of a named plan and the custom package, plus add-ons.

Included in baseline

m2a.large

2 dedicated vCPU, 8 GiB RAM, 0.5 Gbps

$66.00

Compute + RAM rate basis

2 vCPU + 8 GiB RAM at $29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM (regular). Totals apply the flat −$5/mo package promotion.

—

Block storage (90 GiB)

90 GiB at $0.08/GiB/mo

$7.20

Public IP (included)

1 included with the custom package

$0.00

Package promotional discount

Flat −$5.00/mo on the custom package (same promotion as named plans).

$-5.00

Included at no charge

These line items are zero on Quake AI. Many other providers meter them separately.

Data transfer (inbound and outbound)

Unlimited data transfer on every plan; Quake AI does not meter per-GB egress.

AWS, GCP, and Azure meter outbound transfer per GB. DigitalOcean and Hetzner include an allowance on compute plans, then charge overage.

Learn more
$0.00

Private networking

Private networks, subnets, Neutron routers, and security groups are included with the plan.

VPC objects are usually free to create elsewhere, but NAT gateways bill hourly plus per-GB processed. Quake AI uses router SNAT with no separate NAT line item.

$0.00

Control-plane API requests

OpenStack API calls for provisioning and management are included.

Some managed services on other clouds meter API calls or charge for premium control-plane features.

$0.00

Dev/test vs production

Start on shared CPU for dev/test, then promote to dedicated for production with a flavor resize. The network, storage, and template stay the same.

Dev/test on shared CPU

Burstable s1a flavors; suited to prototyping and low or bursty load.

$18.70/mo

Production on dedicated CPU

The headline estimate above; predictable steady-load performance.

$68.20/mo

Saves $49.50/mo while you build on shared CPU.

Shared flavors carry less RAM (m2a.large (8 GiB RAM) -> s1a.small (2 GiB RAM)). A resize reboots the instance; data on attached volumes persists. Size the dedicated flavor for the RAM your production workload needs.

Pricing data last validated: . For current rates, check quake.ai/pricing.

Template source#

7 files. Download the zip or expand to copy any file.Download clickhouse.zip
Show source (7 files)
main.tfHCL
data "openstack_images_image_v2" "os" {
  name        = var.image_name
  most_recent = true
}

data "openstack_networking_network_v2" "external" {
  name = var.external_network
}

resource "openstack_networking_network_v2" "private" {
  name           = "${var.app_name}-net"
  admin_state_up = true
}

resource "openstack_networking_subnet_v2" "private" {
  name            = "${var.app_name}-subnet"
  network_id      = openstack_networking_network_v2.private.id
  cidr            = var.private_cidr
  ip_version      = 4
  dns_nameservers = ["1.1.1.1", "8.8.8.8"]
}

resource "openstack_networking_router_v2" "main" {
  name                = "${var.app_name}-router"
  external_network_id = data.openstack_networking_network_v2.external.id
}

resource "openstack_networking_router_interface_v2" "private" {
  router_id = openstack_networking_router_v2.main.id
  subnet_id = openstack_networking_subnet_v2.private.id
}

resource "openstack_networking_secgroup_v2" "clickhouse" {
  name        = "${var.app_name}-sg"
  description = "SSH and HTTP/HTTPS for a reverse proxy; ClickHouse 8123 and 9000 restricted"
}

resource "openstack_networking_secgroup_rule_v2" "ssh" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 22
  port_range_max    = 22
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = openstack_networking_secgroup_v2.clickhouse.id
}

resource "openstack_networking_secgroup_rule_v2" "http" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 80
  port_range_max    = 80
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = openstack_networking_secgroup_v2.clickhouse.id
}

resource "openstack_networking_secgroup_rule_v2" "https" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 443
  port_range_max    = 443
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = openstack_networking_secgroup_v2.clickhouse.id
}

resource "openstack_networking_secgroup_rule_v2" "clickhouse_http" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 8123
  port_range_max    = 8123
  remote_ip_prefix  = var.client_allowed_cidr
  security_group_id = openstack_networking_secgroup_v2.clickhouse.id
}

resource "openstack_networking_secgroup_rule_v2" "clickhouse_native" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 9000
  port_range_max    = 9000
  remote_ip_prefix  = var.client_allowed_cidr
  security_group_id = openstack_networking_secgroup_v2.clickhouse.id
}

resource "openstack_networking_port_v2" "clickhouse" {
  name               = "${var.app_name}-port"
  network_id         = openstack_networking_network_v2.private.id
  security_group_ids = [openstack_networking_secgroup_v2.clickhouse.id]

  fixed_ip {
    subnet_id = openstack_networking_subnet_v2.private.id
  }

  depends_on = [openstack_networking_router_interface_v2.private]
}

resource "openstack_blockstorage_volume_v3" "data" {
  name = "${var.app_name}-data"
  size = var.volume_size
}

resource "openstack_compute_instance_v2" "clickhouse" {
  name        = var.app_name
  flavor_name = var.flavor_name
  key_pair    = var.key_name

  user_data = templatefile("${path.module}/cloud-init/clickhouse.yaml.tftpl", {
    app_name = var.app_name
  })

  block_device {
    uuid                  = data.openstack_images_image_v2.os.id
    source_type           = "image"
    destination_type      = "volume"
    volume_size           = 40
    boot_index            = 0
    delete_on_termination = true
  }

  network {
    port = openstack_networking_port_v2.clickhouse.id
  }
}

resource "openstack_compute_volume_attach_v2" "data" {
  instance_id = openstack_compute_instance_v2.clickhouse.id
  volume_id   = openstack_blockstorage_volume_v3.data.id
}

resource "openstack_networking_floatingip_v2" "clickhouse" {
  pool = var.external_network
}

resource "openstack_networking_floatingip_associate_v2" "clickhouse" {
  floating_ip = openstack_networking_floatingip_v2.clickhouse.address
  port_id     = openstack_networking_port_v2.clickhouse.id
}
variables.tfHCL
variable "key_name" {
  description = "SSH keypair name (must already exist in your project)"
  type        = string
}

variable "flavor_name" {
  description = "Instance size. ClickHouse analytical workloads run comfortably on 2 vCPU and 8 GiB RAM. Size up for heavier query concurrency or larger in-memory working sets."
  type        = string
  default     = "m2a.large"
}

variable "image_name" {
  description = "Operating system image. Ubuntu 24.04 is the recommended base."
  type        = string
  default     = "Ubuntu-24.04"
}

variable "app_name" {
  description = "Display name prefix for compute and network resources"
  type        = string
  default     = "clickhouse"
}

variable "volume_size" {
  description = "Block volume size in GiB, mounted at /var/lib/docker so ClickHouse table data lives on a volume you can grow rather than on the boot disk."
  type        = number
  default     = 50
}

variable "external_network" {
  description = "Shared external network for router gateway and floating IPs; defaults to PublicStatic (persisted FIP / production pattern). Override with PublicEphemeral for ephemeral demos."
  type        = string
  default     = "PublicStatic"
}

variable "private_cidr" {
  description = "CIDR for the private tenant network the instance lives in"
  type        = string
  default     = "10.49.0.0/24"
}

variable "client_allowed_cidr" {
  description = "CIDR allowed to reach ClickHouse on ports 8123 (HTTP) and 9000 (native). Defaults to the private network only, so the database stays off the public internet on its raw ports. Reach it over an SSH tunnel, from a peer on the private network, or (recommended) put a reverse proxy in front and use HTTPS on 443. To allow direct access from your workstation, set this to YOUR_IP/32."
  type        = string
  default     = "10.49.0.0/24"
}
outputs.tfHCL
output "instance_id" {
  description = "ID of the compute instance running ClickHouse"
  value       = openstack_compute_instance_v2.clickhouse.id
}

output "floating_ip" {
  description = "Public floating IP address of the ClickHouse host"
  value       = openstack_networking_floatingip_v2.clickhouse.address
}

output "private_ip" {
  description = "Private IP address of the instance"
  value       = openstack_compute_instance_v2.clickhouse.access_ip_v4
}

output "http_url" {
  description = "ClickHouse HTTP interface on port 8123. Reachable from client_allowed_cidr (the private network by default; tunnel over SSH, or put a reverse proxy in front and use HTTPS on 443)."
  value       = "http://${openstack_networking_floatingip_v2.clickhouse.address}:8123"
}

output "native_endpoint" {
  description = "ClickHouse native TCP endpoint on port 9000. Same reachability rules as http_url."
  value       = "${openstack_networking_floatingip_v2.clickhouse.address}:9000"
}
versions.tfHCL
terraform {
  required_version = ">= 1.6.0"

  required_providers {
    openstack = {
      source  = "terraform-provider-openstack/openstack"
      version = "~> 2.0"
    }
  }
}

provider "openstack" {}
terraform.tfvars.exampleHCL
# Required: SSH keypair must already exist in your project
key_name = "YOUR_KEY_NAME"

# Recommended: restrict ClickHouse (ports 8123 and 9000) to your workstation IP.
# client_allowed_cidr = "203.0.113.10/32"

# flavor_name = "m2a.large"
# volume_size = 50
cloud-init/clickhouse.yaml.tftpl
#cloud-config
package_update: true
packages:
  - ca-certificates
  - curl
  - openssl
write_files:
  - path: /opt/clickhouse/docker-compose.yml
    permissions: "0644"
    content: |
      services:
        clickhouse:
          image: clickhouse/clickhouse-server:latest
          restart: unless-stopped
          ports:
            - "8123:8123"
            - "9000:9000"
          env_file:
            - /opt/clickhouse/.env
          volumes:
            - clickhouse_data:/var/lib/clickhouse
            - clickhouse_logs:/var/log/clickhouse-server
      volumes:
        clickhouse_data:
        clickhouse_logs:
runcmd:
  - |
    set -e
    DEV=/dev/sdb
    for i in $(seq 1 30); do [ -b "$DEV" ] && break; sleep 5; done
    if ! blkid "$DEV" >/dev/null 2>&1; then mkfs.ext4 -F -L chdata "$DEV"; fi
    mkdir -p /var/lib/docker
    mount "$DEV" /var/lib/docker
    grep -q "$DEV" /etc/fstab || echo "$DEV /var/lib/docker ext4 defaults,nofail 0 2" >> /etc/fstab
    CLICKHOUSE_USER=default
    CLICKHOUSE_PASSWORD=$(openssl rand -hex 24)
    cat > /opt/clickhouse/.env <<EOF
    CLICKHOUSE_USER=$CLICKHOUSE_USER
    CLICKHOUSE_PASSWORD=$CLICKHOUSE_PASSWORD
    CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1
    EOF
    chmod 600 /opt/clickhouse/.env
    printf 'username: %s\npassword: %s\n' "$CLICKHOUSE_USER" "$CLICKHOUSE_PASSWORD" > /opt/clickhouse/.bootstrap-user
    chmod 600 /opt/clickhouse/.bootstrap-user
    curl -fsSL https://get.docker.com | sh
    cd /opt/clickhouse
    docker compose up -d
README.mdMarkdown
# ClickHouse analytical column store

Single compute instance running [ClickHouse](https://clickhouse.com) on infrastructure you control.


**Network class:** production — `external_network` defaults to `PublicStatic` for persisted floating IPs and multi-tier stacks; override with `PublicEphemeral` for ephemeral demos.

See the [template reference page](/resources/iac-templates/clickhouse) and [deployment walkthrough](/resources/deployments/deploy-clickhouse-template).
Resources, parameters, and variables
Provisions
Parameterized by
Variables
  • key_namerequired
  • flavor_name="m2a.large"
  • image_name="Ubuntu-24.04"
  • app_name="clickhouse"
  • volume_size=50
  • external_network="PublicStatic"
  • private_cidr="10.49.0.0/24"
  • client_allowed_cidr="10.49.0.0/24"

Customize this pattern#

See also#

Usage Guidelines

The sample code, software libraries, command line tools, proofs of concept, templates, and other related technology on this page (including any of the foregoing that is provided by Quake AI personnel) is provided to you as Quake AI Content under the Quake AI Customer Agreement, or the relevant written agreement between you and Quake AI (whichever applies). Do not use this Quake AI Content in your production accounts, or on production or other critical data. You are responsible for testing, securing, and optimizing the Quake AI Content (such as sample code) as appropriate for production grade use based on your specific quality control practices and standards. Deploying Quake AI Content may incur Quake AI charges for creating or using Quake AI chargeable resources, such as running Compute instances or storing data in Object Storage. Your use is also subject to the Acceptable Use Policy.

For the full policy, see Usage Guidelines.

Was this page helpful?