Skip to content
IaC Templates

Redis / Valkey cache

Template

Redis / Valkey cache

This pattern composes Compute, Network, and Block Storage into a private cache.

What this template does#

Provisions a single instance running Valkey (or Redis OSS) in Docker on a private network, for use as a cache, session store, or queue backend:

  • Compute instance running the engine container, with the cache bound to the private IP only (not 0.0.0.0)
  • Private network, subnet, router, and security group; the cache port is reachable only from private_cidr
  • No floating IP, so the cache is not exposed to the public internet. SSH is the only rule open to the internet.
  • requirepass authentication set from redis_password, read from a config file mounted read-only
  • Optional append-only persistence on a block volume mounted at /data. Turn it off for an in-memory-only cache.

Valkey is the default engine. Valkey is the BSD-3-Clause licensed fork of Redis, created after Redis OSS moved to the RSALv2 and SSPLv1 source-available license in 2024. To run Redis OSS instead, set redis_image to redis:7-alpine and server_command to redis-server.

Set redis_password to a strong value when you apply; the template ships no default password.

Parameters#

ParameterDescriptionDefault
key_nameSSH keypair name (must already exist)No default
redis_passwordCache password (requirepass), required, no defaultnone
redis_imageEngine imagevalkey/valkey:8-alpine
server_commandServer binary the image runsvalkey-server
flavor_nameInstance sizes1a.small
image_nameOperating system imageUbuntu-24.04
app_nameDisplay name prefix and container nameredis-cache
redis_portPort the cache listens on6379
persistence_enabledAttach a volume and enable append-only persistencetrue
volume_sizePersistence volume size in GiB10
external_networkExternal network for the router gatewayPublicStatic
private_cidrPrivate subnet CIDR; the only range allowed to reach the cache port10.20.0.0/24

When to use this pattern#

Run a cache, session store, or queue backend on a single private instance, reached by an app on the same private network. For a relational datastore, use self-managed PostgreSQL. For the app tier that consumes this cache, see the Next.js app template or Containerized app template.

Estimated cost#

Monthly cost estimate

Pricing calculator ↗

Sized as a custom package on shared vCPU.

Starting template$13.90/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

Cache

s1a.small · 2 shared vCPU, 2 GiB RAM, 0.5 Gbps

$16.50/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

s1a.small

2 shared vCPU, 2 GiB RAM, 0.5 Gbps

$16.50

Compute + RAM rate basis

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

—

Block storage (30 GiB)

30 GiB at $0.08/GiB/mo

$2.40

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

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 redis-cache.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}-private"
  admin_state_up = true
}

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

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

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

resource "openstack_networking_secgroup_v2" "cache" {
  name        = "${var.app_name}-sg"
  description = "SSH from any IPv4; cache port from the private CIDR only"
}

resource "openstack_networking_secgroup_rule_v2" "cache" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = var.redis_port
  port_range_max    = var.redis_port
  remote_ip_prefix  = var.private_cidr
  security_group_id = openstack_networking_secgroup_v2.cache.id
}

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.cache.id
}

resource "openstack_blockstorage_volume_v3" "cache_data" {
  count = var.persistence_enabled ? 1 : 0
  name  = "${var.app_name}-data"
  size  = var.volume_size
}

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

  fixed_ip {
    subnet_id = openstack_networking_subnet_v2.private.id
  }

  depends_on = [openstack_networking_router_interface_v2.private]
}

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

  user_data = templatefile("${path.module}/cloud-init/redis.yaml", {
    redis_image         = var.redis_image
    server_command      = var.server_command
    redis_port          = var.redis_port
    redis_password      = var.redis_password
    app_name            = var.app_name
    persistence_enabled = var.persistence_enabled
    appendonly          = var.persistence_enabled ? "yes" : "no"
  })

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

  network {
    port = openstack_networking_port_v2.cache.id
  }
}

resource "openstack_compute_volume_attach_v2" "cache_data" {
  count       = var.persistence_enabled ? 1 : 0
  instance_id = openstack_compute_instance_v2.cache.id
  volume_id   = openstack_blockstorage_volume_v3.cache_data[0].id
}
variables.tfHCL
variable "key_name" {
  description = "SSH keypair name (must already exist in your project)"
  type        = string
}

variable "redis_password" {
  description = "Password for the cache (requirepass). Required, no default, so no credential ships with the template."
  type        = string
  sensitive   = true
}

variable "redis_image" {
  description = "Container image for the cache engine. Defaults to Valkey, the BSD-licensed Redis fork. For Redis OSS, set this to redis:7-alpine and set server_command to redis-server."
  type        = string
  default     = "valkey/valkey:8-alpine"
}

variable "server_command" {
  description = "Server binary the image runs. valkey-server for Valkey, redis-server for Redis OSS."
  type        = string
  default     = "valkey-server"
}

variable "flavor_name" {
  description = "Instance size"
  type        = string
  default     = "s1a.small"
}

variable "image_name" {
  description = "Operating system image"
  type        = string
  default     = "Ubuntu-24.04"
}

variable "app_name" {
  description = "Display name prefix for compute and network resources, and the container name"
  type        = string
  default     = "redis-cache"
}

variable "redis_port" {
  description = "Port the cache listens on, bound to the private interface only"
  type        = number
  default     = 6379
}

variable "persistence_enabled" {
  description = "When true, attach a block volume and enable append-only persistence. When false, the cache is in-memory only and data is lost on restart."
  type        = bool
  default     = true
}

variable "volume_size" {
  description = "Block volume size in GiB for persistence (used only when persistence_enabled is true)"
  type        = number
  default     = 10
}

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 cache port is reachable only from this range."
  type        = string
  default     = "10.20.0.0/24"
}
outputs.tfHCL
output "instance_id" {
  description = "ID of the compute instance running the cache"
  value       = openstack_compute_instance_v2.cache.id
}

output "private_ip" {
  description = "Private IP address of the cache instance. Reach the cache from peers on the private network."
  value       = openstack_compute_instance_v2.cache.access_ip_v4
}

output "connection_string_shape" {
  description = "Connection string shape for clients. Substitute your own password for REDIS_PASSWORD; the secret is not emitted here."
  value       = "redis://:REDIS_PASSWORD@${openstack_compute_instance_v2.cache.access_ip_v4}:${var.redis_port}/0"
}
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"

# Required: the cache password (requirepass). No default ships with the template.
redis_password = "YOUR_STRONG_PASSWORD"

# Optional: engine and sizing
# redis_image = "valkey/valkey:8-alpine"   # for Redis OSS: "redis:7-alpine"
# server_command = "valkey-server"          # for Redis OSS: "redis-server"
# flavor_name = "s1a.small"
# image_name = "Ubuntu-24.04"
# app_name = "redis-cache"
# redis_port = 6379

# Optional: persistence (append-only on a block volume). Set false for in-memory only.
# persistence_enabled = true
# volume_size = 10

# Optional: networking
# external_network = "PublicStatic"
# private_cidr = "10.20.0.0/24"
cloud-init/redis.yamlYAML
#cloud-config
package_update: true
packages:
  - ca-certificates
  - curl
write_files:
  - path: /etc/valkey/valkey.conf
    owner: root:root
    permissions: "0644"
    content: |
      port ${redis_port}
      requirepass ${redis_password}
      appendonly ${appendonly}
      dir /data
runcmd:
  - |
    set -e
    %{ if persistence_enabled ~}
    # The data volume attaches as /dev/sdb on this platform (not /dev/vdb).
    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 redisdata "$DEV"; fi
    mkdir -p /data
    mount "$DEV" /data
    grep -q "$DEV" /etc/fstab || echo "$DEV /data ext4 defaults,nofail 0 2" >> /etc/fstab
    %{ else ~}
    mkdir -p /data
    %{ endif ~}
    PRIVATE_IP=$(hostname -I | awk '{print $1}')
    curl -fsSL https://get.docker.com | sh
    systemctl enable --now docker
    docker pull ${redis_image}
    docker run -d --name ${app_name} --restart unless-stopped \
      -p $PRIVATE_IP:${redis_port}:${redis_port} \
      -v /data:/data \
      -v /etc/valkey/valkey.conf:/etc/valkey/valkey.conf:ro \
      ${redis_image} ${server_command} /etc/valkey/valkey.conf
README.mdMarkdown
# Redis / Valkey cache

Single compute instance running Valkey (or Redis OSS) in Docker on a private network, for use as a cache, session store, or queue backend. The cache port is reachable only from the private CIDR; the instance has no floating IP. This is the self-hosted equivalent of a hosted Redis service.


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

Valkey is the default engine. Valkey is the BSD-3-Clause licensed fork of Redis created after Redis OSS moved to the RSALv2 / SSPLv1 source-available license in 2024. To run Redis OSS instead, set `redis_image = "redis:7-alpine"` and `server_command = "redis-server"`.

## quake.yaml mapping

A `redis` service in a launch manifest resolves to this template. The launch handoff applies it before the runtime and passes the resolved connection details to the app as a named environment variable.

| `quake.yaml` field | Maps to |
| --- | --- |
| `services: [{ type: redis }]` | This template |
| resolved `private_ip` and `redis_port` | A `REDIS_URL` injected into the runtime's `container_env` (you supply the password) |

## Prerequisites

- OpenTofu >= 1.6.0 or Terraform >= 1.6.0
- Quake AI account with OpenStack credentials
- An existing SSH keypair in your project (the value of `key_name` must match that keypair)
- A peer on the same private network to reach the cache (an app instance, or a bastion). The cache is not exposed on a floating IP.

## Usage

1. Clone or copy this template directory
2. Copy `terraform.tfvars.example` to `terraform.tfvars` and set `key_name` and `redis_password`
3. Source your OpenStack credentials: `source openrc.sh`
4. Initialize: `tofu init`
5. Preview: `tofu plan`
6. Apply: `tofu apply`

Read `private_ip` and `connection_string_shape` from the outputs. Substitute your password for `REDIS_PASSWORD` in the shape to build the client URL.

## How the instance runs the cache

cloud-init installs Docker, optionally formats and mounts a block volume at `/data`, then runs the engine container:

- The cache binds to the instance's private IP only (`-p PRIVATE_IP:6379:6379`), not `0.0.0.0`.
- `requirepass` is set from `redis_password`, read from a config file mounted read-only.
- With `persistence_enabled = true`, append-only persistence writes to `/data` on the attached volume. With it false, the cache is in-memory only and data is lost on restart.

## Security

- The security group allows the cache port only from `private_cidr`. SSH (22) is the only rule open to `0.0.0.0/0`, and the instance has no floating IP, so the cache is not reachable from the public internet.
- `redis_password` is required with no default, so no credential ships with the template. The password is rendered into the instance's cloud-init data (as with any self-provisioning template); it is not committed to the repository.

## Variables

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `key_name` | string | yes | n/a | SSH keypair name (must already exist in your project) |
| `redis_password` | string | yes | n/a | Cache password (`requirepass`) |
| `redis_image` | string | no | `valkey/valkey:8-alpine` | Engine image (Redis OSS: `redis:7-alpine`) |
| `server_command` | string | no | `valkey-server` | Server binary (Redis OSS: `redis-server`) |
| `flavor_name` | string | no | `s1a.small` | Instance size |
| `image_name` | string | no | `Ubuntu-24.04` | Operating system image |
| `app_name` | string | no | `redis-cache` | Display name prefix and container name |
| `redis_port` | number | no | `6379` | Port the cache listens on |
| `persistence_enabled` | bool | no | `true` | Attach a volume and enable append-only persistence |
| `volume_size` | number | no | `10` | Persistence volume size in GiB |
| `external_network` | string | no | `PublicStatic` | Persisted FIP / production default; override with `PublicEphemeral` for demos |
| `private_cidr` | string | no | `10.20.0.0/24` | Private subnet CIDR; the only range allowed to reach the cache port |

## Outputs

| Name | Description |
| --- | --- |
| `private_ip` | Private IP of the cache instance |
| `connection_string_shape` | Client URL shape with a `REDIS_PASSWORD` placeholder (no secret emitted) |
| `instance_id` | Compute instance ID |

## Scope

Single-instance cache, not a replicated or highly available cluster. For high availability, run Sentinel or a clustered topology across multiple instances; this template provisions one node.

## Documentation

See also: [self-managed Postgres template](/resources/iac-templates/self-managed-postgres)
Resources, parameters, and variables
Provisions
Parameterized by
Variables
  • key_namerequired
  • redis_passwordrequired
  • redis_image="valkey/valkey:8-alpine"
  • server_command="valkey-server"
  • flavor_name="s1a.small"
  • image_name="Ubuntu-24.04"
  • app_name="redis-cache"
  • redis_port=6379
  • persistence_enabled=true
  • volume_size=10
  • external_network="PublicStatic"
  • private_cidr="10.20.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?