Skip to content

Feast feature store

Template · Updated Jul 2026
Validated Jul 2026

Feast feature store

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

What this template does#

Provisions a single instance running Feast, an open-source feature store for training and serving ML features:

  • Docker stack with a Feast feature server on port 6566, plus bundled Postgres (offline store and SQL registry) and Redis (online store) when store_mode is bundled
  • Default sizing: s1a.small (2 vCPU / 2 GiB RAM) and a 30 GiB data volume at /var/lib/docker for container layers and database files
  • Private network, security group, floating IP; feature server port 6566 restricted to server_allowed_cidr by default
  • Sample driver_hourly_stats feature view and seed rows in Postgres for smoke tests after first boot

Feast keeps offline training data, online serving lookups, and registry metadata in stores you operate. Point store_mode at external and set postgres_host and redis_host to wire in self-managed PostgreSQL and Redis cache instead of the bundled containers.

No credential ships with this template. cloud-init generates a Postgres password on first boot and writes it to /opt/feast/.env on the instance. For external stores, add FEAST_DB_PASSWORD there after apply.

Parameters#

ParameterDescriptionDefault
key_nameSSH keypair name (must already exist)No default
flavor_nameInstance size (bundled Postgres + Redis on 2 vCPU / 2 GiB)s1a.small
image_nameOperating system imageUbuntu-24.04
app_nameDisplay name prefix for resourcesfeast
volume_sizeBlock volume size in GiB, mounted at /var/lib/docker30
external_networkExternal network for floating IP allocationPublicStatic
private_cidrCIDR for the private subnet10.55.0.0/24
server_allowed_cidrCIDR allowed to reach the feature server on port 656610.55.0.0/24
store_modebundled (Postgres + Redis on this VM) or externalbundled
postgres_hostPostgreSQL host when store_mode is external""
redis_hostRedis host when store_mode is external""
postgres_portPostgreSQL port when store_mode is external5432
postgres_databasePostgreSQL database when store_mode is externalfeast
postgres_userPostgreSQL user when store_mode is externalfeast
redis_portRedis port when store_mode is external6379

Feature server access and security#

The Feast feature server listens on port 6566 (gRPC). The security group restricts 6566 to server_allowed_cidr, which defaults to the private network only. Reach the server over an SSH tunnel, through a reverse proxy on 443, or by setting server_allowed_cidr to YOUR_IP/32.

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

Store modes#

The store_mode parameter selects where Feast keeps data:

  • bundled (default): Postgres holds the offline store, SQL registry, and sample driver_stats table; Redis holds the online store. Both run as containers on the data volume.
  • external: only the Feast server containers run on this VM. Set postgres_host and redis_host to private IPs from your Postgres and Redis stacks, create the feast database and driver_stats table on Postgres, then add FEAST_DB_PASSWORD to /opt/feast/.env and run docker compose up -d in /opt/feast.

When to use this pattern#

Run a feature store that serves low-latency online features to inference workloads and materializes historical features from Postgres for training. Feast suits the feature layer in a notebook workbench or pipeline stack alongside experiment tracking and a warehouse.

For the warehouse itself, see self-managed PostgreSQL. For the online cache layer, see Redis cache.

Estimated cost#

Monthly cost estimate

Pricing calculator ↗

Sized as a custom package on shared vCPU.

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

Feast

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 (60 GiB)

60 GiB at $0.08/GiB/mo

$4.80

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

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

Template source#

6 files. Download the zip or expand to copy any file.Download feast.zip
Show source (6 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" "feast" {
  name        = "${var.app_name}-sg"
  description = "SSH and HTTP/HTTPS for a reverse proxy; feature server port 6566 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.feast.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.feast.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.feast.id
}

resource "openstack_networking_secgroup_rule_v2" "feature_server" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 6566
  port_range_max    = 6566
  remote_ip_prefix  = var.server_allowed_cidr
  security_group_id = openstack_networking_secgroup_v2.feast.id
}

resource "openstack_networking_port_v2" "feast" {
  name               = "${var.app_name}-port"
  network_id         = openstack_networking_network_v2.private.id
  security_group_ids = [openstack_networking_secgroup_v2.feast.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" "feast" {
  name        = var.app_name
  flavor_name = var.flavor_name
  key_pair    = var.key_name

  user_data = templatefile("${path.module}/cloud-init/feast.yaml.tftpl", {
    app_name          = var.app_name
    feast_project     = replace(var.app_name, "-", "_")
    store_mode        = var.store_mode
    postgres_host     = var.postgres_host
    postgres_port     = var.postgres_port
    postgres_database = var.postgres_database
    postgres_user     = var.postgres_user
    redis_host        = var.redis_host
    redis_port        = var.redis_port
  })

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

  network {
    port = openstack_networking_port_v2.feast.id
  }
}

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

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

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

variable "flavor_name" {
  description = "Instance size. Feast with bundled Postgres and Redis runs on 2 vCPU and 2 GiB RAM."
  type        = string
  default     = "s1a.small"
}

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     = "feast"
}

variable "volume_size" {
  description = "Block volume size in GiB, mounted at /var/lib/docker so registry data, Postgres, and Redis volumes live on storage you can grow."
  type        = number
  default     = 30
}

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.55.0.0/24"
}

variable "server_allowed_cidr" {
  description = "CIDR allowed to reach the Feast feature server on port 6566. Defaults to the private network only. Reach it over an SSH tunnel, or put a reverse proxy in front. To allow direct access from your workstation, set this to YOUR_IP/32."
  type        = string
  default     = "10.55.0.0/24"
}

variable "store_mode" {
  description = "Feature store backing services. bundled runs Postgres and Redis in Docker on this VM. external points Feast at existing Postgres and Redis hosts (for example self-managed-postgres and redis-cache templates)."
  type        = string
  default     = "bundled"

  validation {
    condition     = contains(["bundled", "external"], var.store_mode)
    error_message = "store_mode must be either \"bundled\" or \"external\"."
  }
}

variable "postgres_host" {
  description = "PostgreSQL host for store_mode = external (offline store and SQL registry). Ignored when store_mode is bundled."
  type        = string
  default     = ""
}

variable "postgres_port" {
  description = "PostgreSQL port for store_mode = external."
  type        = number
  default     = 5432
}

variable "postgres_database" {
  description = "PostgreSQL database for store_mode = external."
  type        = string
  default     = "feast"
}

variable "postgres_user" {
  description = "PostgreSQL user for store_mode = external. Add FEAST_DB_PASSWORD to /opt/feast/.env on the instance; never in tfvars."
  type        = string
  default     = "feast"
}

variable "redis_host" {
  description = "Redis host for store_mode = external. Ignored when store_mode is bundled."
  type        = string
  default     = ""
}

variable "redis_port" {
  description = "Redis port for store_mode = external."
  type        = number
  default     = 6379
}
outputs.tfHCL
output "instance_id" {
  description = "ID of the compute instance running Feast"
  value       = openstack_compute_instance_v2.feast.id
}

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

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

output "feature_server_url" {
  description = "Feast feature server endpoint on port 6566. Reachable from server_allowed_cidr (the private network by default)."
  value       = "${openstack_networking_floatingip_v2.feast.address}:6566"
}

output "store_mode" {
  description = "Whether Postgres and Redis run bundled on this VM or point at external hosts"
  value       = var.store_mode
}
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 the feature server (port 6566) to your workstation IP.
# Leave unset to keep 6566 reachable only from the private network and tunnel
# over SSH, or put a reverse proxy in front.
# server_allowed_cidr = "203.0.113.10/32"

# Store mode: bundled (default, Postgres + Redis in Docker) or external.
# For external, set postgres_host and redis_host to private IPs from the
# self-managed-postgres and redis-cache templates, then add FEAST_DB_PASSWORD
# to /opt/feast/.env on the instance after apply.
# store_mode        = "external"
# postgres_host     = "10.55.0.12"
# postgres_database = "feast"
# postgres_user     = "feast"
# redis_host        = "10.55.0.13"

# flavor_name = "s1a.small"
# image_name = "Ubuntu-24.04"
# app_name = "feast"
# volume_size = 30
# external_network = "PublicStatic"
# private_cidr = "10.55.0.0/24"
cloud-init/feast.yaml.tftpl
#cloud-config
package_update: true
packages:
  - ca-certificates
  - curl
  - openssl
write_files:
  - path: /opt/feast/docker-compose.yml
    permissions: "0644"
    content: |
      services:
%{ if store_mode == "bundled" ~}
        postgres:
          image: postgres:15
          restart: unless-stopped
          environment:
            POSTGRES_USER: feast
            POSTGRES_PASSWORD: $${FEAST_DB_PASSWORD}
            POSTGRES_DB: feast
          volumes:
            - postgres_data:/var/lib/postgresql/data
            - /opt/feast/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
          networks: [feast-net]
          healthcheck:
            test: ["CMD-SHELL", "pg_isready -U feast -d feast"]
            interval: 5s
            timeout: 5s
            retries: 12
        redis:
          image: redis:7-alpine
          restart: unless-stopped
          networks: [feast-net]
          healthcheck:
            test: ["CMD", "redis-cli", "ping"]
            interval: 5s
            timeout: 5s
            retries: 12
%{ endif ~}
        feast-init:
          image: python:3.11-slim
          working_dir: /feature_repo
          volumes:
            - /opt/feast/feature_repo:/feature_repo
          env_file:
            - /opt/feast/.env
          entrypoint: /bin/bash
          command:
            - -c
            - |
              set -e
              pip install -q 'feast[postgres,redis]==0.47.0' psycopg2-binary
              feast apply
              feast materialize $$(date -u -d '2 days ago' +%Y-%m-%dT%H:%M:%S) $$(date -u +%Y-%m-%dT%H:%M:%S)
          networks: [feast-net]
%{ if store_mode == "bundled" ~}
          depends_on:
            postgres:
              condition: service_healthy
            redis:
              condition: service_healthy
%{ endif ~}
        feast-server:
          image: python:3.11-slim
          restart: unless-stopped
          working_dir: /feature_repo
          ports:
            - "6566:6566"
          volumes:
            - /opt/feast/feature_repo:/feature_repo
          env_file:
            - /opt/feast/.env
          command:
            - bash
            - -c
            - |
              set -e
              pip install -q 'feast[postgres,redis]==0.47.0' psycopg2-binary
              exec feast serve -h 0.0.0.0 -p 6566
          networks: [feast-net]
          depends_on:
            feast-init:
              condition: service_completed_successfully
      volumes:
        postgres_data: {}
      networks:
        feast-net:
          driver: bridge
  - path: /opt/feast/init.sql
    permissions: "0644"
    content: |
      CREATE TABLE IF NOT EXISTS driver_stats (
        driver_id INTEGER NOT NULL,
        event_timestamp TIMESTAMP NOT NULL,
        created TIMESTAMP NOT NULL DEFAULT NOW(),
        conv_rate REAL,
        acc_rate REAL,
        avg_daily_trips INTEGER
      );
      INSERT INTO driver_stats (driver_id, event_timestamp, created, conv_rate, acc_rate, avg_daily_trips)
      VALUES
        (1001, NOW() - INTERVAL '1 hour', NOW(), 0.77, 0.91, 320),
        (1002, NOW() - INTERVAL '2 hours', NOW(), 0.84, 0.88, 280),
        (1003, NOW() - INTERVAL '30 minutes', NOW(), 0.73, 0.95, 410);
  - path: /opt/feast/feature_repo/features.py
    permissions: "0644"
    content: |
      from datetime import timedelta

      from feast import Entity, FeatureView, Field
      from feast.infra.offline_stores.contrib.postgres_offline_store.postgres_source import (
          PostgreSQLSource,
      )
      from feast.types import Float32, Int64

      driver = Entity(name="driver", join_keys=["driver_id"])

      driver_stats_source = PostgreSQLSource(
          name="driver_stats",
          query=(
              "SELECT driver_id, event_timestamp, created, conv_rate, acc_rate, "
              "avg_daily_trips FROM driver_stats"
          ),
          timestamp_field="event_timestamp",
          created_timestamp_column="created",
      )

      driver_hourly_stats = FeatureView(
          name="driver_hourly_stats",
          entities=[driver],
          ttl=timedelta(hours=24),
          schema=[
              Field(name="conv_rate", dtype=Float32),
              Field(name="acc_rate", dtype=Float32),
              Field(name="avg_daily_trips", dtype=Int64),
          ],
          source=driver_stats_source,
      )
runcmd:
  - |
    set -eu
    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 feastdata "$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
    curl -fsSL https://get.docker.com | sh
    systemctl enable --now docker
    for i in $(seq 1 30); do docker info >/dev/null 2>&1 && break; sleep 5; done
    STORE_MODE="${store_mode}"
    POSTGRES_HOST="${postgres_host}"
    POSTGRES_PORT="${postgres_port}"
    POSTGRES_DATABASE="${postgres_database}"
    POSTGRES_USER="${postgres_user}"
    REDIS_HOST="${redis_host}"
    REDIS_PORT="${redis_port}"
    if [ -f /opt/feast/.env ] && grep -q '^FEAST_DB_PASSWORD=' /opt/feast/.env; then
      FEAST_DB_PASSWORD="$(grep '^FEAST_DB_PASSWORD=' /opt/feast/.env | cut -d= -f2-)"
    else
      FEAST_DB_PASSWORD="$(openssl rand -hex 24)"
      umask 077
      printf 'FEAST_DB_PASSWORD=%s\n' "$FEAST_DB_PASSWORD" > /opt/feast/.env
    fi
    if [ "$STORE_MODE" = "bundled" ]; then
      PG_HOST="postgres"
      PG_PORT="5432"
      PG_DB="feast"
      PG_USER="feast"
      REDIS_CONN="redis:6379,db=0"
    else
      PG_HOST="$POSTGRES_HOST"
      PG_PORT="$POSTGRES_PORT"
      PG_DB="$POSTGRES_DATABASE"
      PG_USER="$POSTGRES_USER"
      REDIS_CONN="$REDIS_HOST:$REDIS_PORT,db=0"
    fi
    install -d -m 0755 /opt/feast/feature_repo
    {
      printf '%s\n' "project: ${feast_project}"
      printf '%s\n' "registry:"
      printf '%s\n' "  registry_type: sql"
      printf '%s\n' "  path: postgresql+psycopg2://$${PG_USER}:$${FEAST_DB_PASSWORD}@$${PG_HOST}:$${PG_PORT}/$${PG_DB}?sslmode=disable"
      printf '%s\n' "provider: local"
      printf '%s\n' "offline_store:"
      printf '%s\n' "  type: postgres"
      printf '%s\n' "  host: $${PG_HOST}"
      printf '%s\n' "  port: $${PG_PORT}"
      printf '%s\n' "  database: $${PG_DB}"
      printf '%s\n' "  db_schema: public"
      printf '%s\n' "  user: $${PG_USER}"
      printf '%s\n' "  password: $${FEAST_DB_PASSWORD}"
      printf '%s\n' "  sslmode: disable"
      printf '%s\n' "online_store:"
      printf '%s\n' "  type: redis"
      printf '%s\n' "  connection_string: $${REDIS_CONN}"
      printf '%s\n' "entity_key_serialization_version: 2"
    } > /opt/feast/feature_repo/feature_store.yaml
    cd /opt/feast
    if [ "$STORE_MODE" = "bundled" ]; then
      docker compose up -d postgres redis
      for i in $(seq 1 60); do
        docker compose exec -T postgres pg_isready -U feast -d feast >/dev/null 2>&1 && break
        sleep 5
      done
    fi
    docker compose up -d --wait feast-init
    init_ec=$?
    docker compose logs feast-init 2>&1 | tail -20 || true
    if [ "$init_ec" -eq 0 ]; then
      docker compose up -d --no-deps feast-server
    else
      echo "feast-init exit $${init_ec}; skipping feast-server" >&2
    fi
Resources, parameters, and variables
Provisions
Parameterized by
Variables
  • key_namerequired
  • flavor_name="s1a.small"
  • image_name="Ubuntu-24.04"
  • app_name="feast"
  • volume_size=30
  • external_network="PublicStatic"
  • private_cidr="10.55.0.0/24"
  • server_allowed_cidr="10.55.0.0/24"
  • store_mode="bundled" validation {
  • postgres_host=""
  • postgres_port=5432
  • postgres_database="feast"
  • postgres_user="feast"
  • redis_host=""
  • redis_port=6379

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.

Last validated: 07.07.2026

Was this page helpful?