diff --git a/examples/deployments/common/startup.sh b/examples/deployments/common/startup.sh new file mode 100644 index 00000000000..a6e5b3134f3 --- /dev/null +++ b/examples/deployments/common/startup.sh @@ -0,0 +1,53 @@ +#! /bin/bash + +# Note: This is run as root + +cd ~ +export enable_auth="${enable_auth}" +export basic_auth_credentials="${basic_auth_credentials}" +export auth_type="${auth_type}" +export token_auth_credentials="${token_auth_credentials}" +apt-get update -y +apt-get install -y ca-certificates curl gnupg lsb-release +mkdir -m 0755 -p /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null +apt-get update -y +chmod a+r /etc/apt/keyrings/docker.gpg +apt-get update -y +apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin git +usermod -aG docker ubuntu +git clone https://github.com/chroma-core/chroma.git && cd chroma +git fetch --tags +git checkout tags/${chroma_release} + +if [ "$${enable_auth}" = "true" ] && [ "$${auth_type}" = "basic" ] && [ ! -z "$${basic_auth_credentials}" ]; then + username=$(echo $basic_auth_credentials | cut -d: -f1) + password=$(echo $basic_auth_credentials | cut -d: -f2) + docker run --rm --entrypoint htpasswd httpd:2 -Bbn $username $password > server.htpasswd + cat < .env +CHROMA_SERVER_AUTH_CREDENTIALS_FILE="/chroma/server.htpasswd" +CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER='chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider' +CHROMA_SERVER_AUTH_PROVIDER='chromadb.auth.basic.BasicAuthServerProvider' +EOF +fi + +if [ "$${enable_auth}" = "true" ] && [ "$${auth_type}" = "token" ] && [ ! -z "$${token_auth_credentials}" ]; then + cat < .env +CHROMA_SERVER_AUTH_CREDENTIALS="$${token_auth_credentials}" \ +CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER='chromadb.auth.token.TokenConfigServerAuthCredentialsProvider' +CHROMA_SERVER_AUTH_PROVIDER='chromadb.auth.token.TokenAuthServerProvider' +EOF +fi + +cat < docker-compose.override.yaml +version: '3.8' +services: + server: + volumes: + - /chroma-data:/chroma/chroma +EOF + +COMPOSE_PROJECT_NAME=chroma docker compose up -d --build diff --git a/examples/deployments/do-terraform/README.md b/examples/deployments/do-terraform/README.md new file mode 100644 index 00000000000..80957bdd810 --- /dev/null +++ b/examples/deployments/do-terraform/README.md @@ -0,0 +1,163 @@ +# Digital Ocean Droplet Deployment + +This is an example deployment using Digital Ocean Droplet using [terraform](https://www.terraform.io/). + +This deployment will do the following: + +- 🔥 Create a firewall with required ports open (22 and 8000) +- 🐳 Create Droplet with Ubuntu 22 and deploy Chroma using docker compose +- 💿 Create a data volume for Chroma data +- 🗻 Mount the data volume to the Droplet instance +- ✏️ Format the data volume with ext4 +- 🏃‍ Start Chroma + +## Requirements + +- [Terraform CLI v1.3.4+](https://developer.hashicorp.com/terraform/tutorials/gcp-get-started/install-cli) + +## Deployment with terraform + +This deployment uses Ubuntu 22 as foundation, but you'd like to use a different image for your Droplet ( +see https://slugs.do-api.dev/ for a list of available images) + +### Configuration Options + + +### 1. Init your terraform state + +```bash +terraform init +``` + +### 2. Deploy your application + +Generate SSH key to use with your chroma instance (so you can log in to the Droplet): + +> Note: This is optional. You can use your own existing SSH key if you prefer. + +```bash +ssh-keygen -t RSA -b 4096 -C "Chroma DO Key" -N "" -f ./chroma-do && chmod 400 ./chroma-do +``` + +Set up your Terraform variables and deploy your instance: + +```bash +#take note of this as it must be present in all of the subsequent steps +export TF_VAR_do_token= +#path to the public key you generated above (or can be different if you want to use your own key) +export TF_ssh_public_key="./chroma-do.pub" +#path to the private key you generated above (or can be different if you want to use your own key) - used for formatting the Chroma data volume +export TF_ssh_private_key="./chroma-do" +#set the chroma release to deploy +export TF_VAR_chroma_release="0.4.12" +# DO region to deploy the chroma instance to +export TF_VAR_region="ams2" +#enable public access to the chroma instance on port 8000 +export TF_VAR_public_access="true" +#enable basic auth for the chroma instance +export TF_VAR_enable_auth="true" +#The auth type to use for the chroma instance (token or basic) +export TF_VAR_auth_type="token" +terraform apply -auto-approve +``` + +> Note: Basic Auth is supported by Chroma v0.4.7+ + +### 4. Check your public IP and that Chroma is running + +Get the public IP of your instance + +```bash +terraform output instance_public_ip +``` + +Check that chroma is running (It should take up several minutes for the instance to be ready) + +```bash +export instance_public_ip=$(terraform output instance_public_ip | sed 's/"//g') +curl -v http://$instance_public_ip:8000/api/v1/heartbeat +``` + +#### 4.1 Checking Auth + +##### Token + +When token auth is enabled you can check the get the credentials from Terraform state by running: + +```bash +terraform output chroma_auth_token +``` + +You should see something of the form: + +```bash +PVcQ4qUUnmahXwUgAf3UuYZoMlos6MnF +``` + +You can then export these credentials: + +```bash +export CHROMA_AUTH=$(terraform output chroma_auth_token | sed 's/"//g') +``` + +Using the credentials: + +```bash +curl -v http://$instance_public_ip:8000/api/v1/collections -H "Authorization: Bearer ${CHROMA_AUTH}" +``` + +##### Basic + +When basic auth is enabled you can check the get the credentials from Terraform state by running: + +```bash +terraform output chroma_auth_basic +``` + +You should see something of the form: + +```bash +chroma:VuA8I}QyNrm0@QLq +``` + +You can then export these credentials: + +```bash +export CHROMA_AUTH=$(terraform output chroma_auth_basic | sed 's/"//g') +``` + +Using the credentials: + +```bash +curl -v http://$instance_public_ip:8000/api/v1/collections -u "${CHROMA_AUTH}" +``` + +> Note: Without `-u` you should be getting 401 Unauthorized response + +#### 4.2 SSH to your instance + +To SSH to your instance: + +```bash +ssh -i ./chroma-do root@$instance_public_ip +``` + +### 5. Destroy your Chroma instance + +```bash +terraform destroy -auto-approve +``` + +## Extras + +You can visualize your infrastructure with: + +```bash +terraform graph | dot -Tsvg > graph.svg +``` + +> Note: You will need graphviz installed for this to work + +### Digital Ocean Resource Types + +Refs: https://slugs.do-api.dev/ diff --git a/examples/deployments/do-terraform/chroma.tf b/examples/deployments/do-terraform/chroma.tf new file mode 100644 index 00000000000..79960c80fe9 --- /dev/null +++ b/examples/deployments/do-terraform/chroma.tf @@ -0,0 +1,133 @@ +terraform { + required_providers { + digitalocean = { + source = "digitalocean/digitalocean" + version = "~> 2.0" + } + } +} + +# Define provider +variable "do_token" {} + +# Configure the DigitalOcean Provider +provider "digitalocean" { + token = var.do_token +} + + +resource "digitalocean_firewall" "chroma_firewall" { + name = "chroma-firewall" + + droplet_ids = [digitalocean_droplet.chroma_instance.id] + + inbound_rule { + protocol = "tcp" + port_range = "22" + source_addresses = var.mgmt_source_ranges + } + + dynamic "inbound_rule" { + for_each = var.public_access ? [1] : [] + content { + protocol = "tcp" + port_range = var.chroma_port + source_addresses = var.source_ranges + } + } + + outbound_rule { + protocol = "tcp" + port_range = "1-65535" + destination_addresses = ["0.0.0.0/0", "::/0"] + } + + outbound_rule { + protocol = "icmp" + port_range = "1-65535" + destination_addresses = ["0.0.0.0/0", "::/0"] + } + + outbound_rule { + protocol = "udp" + port_range = "1-65535" + destination_addresses = ["0.0.0.0/0", "::/0"] + } + + tags = local.tags + +} + +resource "digitalocean_ssh_key" "chroma_keypair" { + name = "chroma_keypair" + public_key = file(var.ssh_public_key) +} + + +#Create Droplet +resource "digitalocean_droplet" "chroma_instance" { + image = var.instance_image + name = "chroma" + region = var.region + size = var.instance_type + ssh_keys = [digitalocean_ssh_key.chroma_keypair.fingerprint] + + user_data = data.template_file.user_data.rendered + + tags = local.tags +} + + +resource "digitalocean_volume" "chroma_volume" { + region = digitalocean_droplet.chroma_instance.region + name = "chroma-volume" + size = var.chroma_data_volume_size + description = "Chroma data volume" + tags = local.tags +} + +resource "digitalocean_volume_attachment" "chroma_data_volume_attachment" { + droplet_id = digitalocean_droplet.chroma_instance.id + volume_id = digitalocean_volume.chroma_volume.id + + provisioner "remote-exec" { + inline = [ + "export VOLUME_ID=${digitalocean_volume.chroma_volume.name} && sudo mkfs -t ext4 /dev/$(lsblk -o +SERIAL | grep $VOLUME_ID | awk '{print $1}')", + "sudo mkdir /chroma-data", + "export VOLUME_ID=${digitalocean_volume.chroma_volume.name} && sudo mount /dev/$(lsblk -o +SERIAL | grep $VOLUME_ID | awk '{print $1}') /chroma-data", + "cat <> /dev/null", + "/dev/disk/by-id/scsi-0DO_Volume_${digitalocean_volume.chroma_volume.name} /chroma-data ext4 defaults,nofail,discard 0 0", + "EOF", + ] + + connection { + host = digitalocean_droplet.chroma_instance.ipv4_address + type = "ssh" + user = "root" + private_key = file(var.ssh_private_key) + } + } +} + + +output "instance_public_ip" { + value = digitalocean_droplet.chroma_instance.ipv4_address + description = "The public IP address of the Chroma instance" +} + +output "instance_private_ip" { + value = digitalocean_droplet.chroma_instance.ipv4_address_private + description = "The private IP address of the Chroma instance" +} + +output "chroma_auth_token" { + description = "The Chroma static auth token" + value = random_password.chroma_token.result + sensitive = true +} + +output "chroma_auth_basic" { + description = "The Chroma basic auth credentials" + value = "${local.basic_auth_credentials.username}:${local.basic_auth_credentials.password}" + sensitive = true +} diff --git a/examples/deployments/do-terraform/variables.tf b/examples/deployments/do-terraform/variables.tf new file mode 100644 index 00000000000..75ce6dc9a37 --- /dev/null +++ b/examples/deployments/do-terraform/variables.tf @@ -0,0 +1,126 @@ +variable "instance_image" { + description = "The image to use for the instance" + type = string + default = "ubuntu-22-04-x64" +} +variable "chroma_release" { + description = "The chroma release to deploy" + type = string + default = "0.4.12" +} + +data "http" "startup_script_remote" { + url = "https://raw.githubusercontent.com/chroma-core/chroma/main/examples/deployments/common/startup.sh" +} + +data "template_file" "user_data" { + template = data.http.startup_script_remote.response_body + + vars = { + chroma_release = var.chroma_release + enable_auth = var.enable_auth + auth_type = var.auth_type + basic_auth_credentials = "${local.basic_auth_credentials.username}:${local.basic_auth_credentials.password}" + token_auth_credentials = random_password.chroma_token.result + } +} + +variable "region" { + description = "DO Region" + type = string + default = "nyc2" +} + +variable "instance_type" { + description = "Droplet size" + type = string + default = "s-2vcpu-4gb" +} + + +variable "public_access" { + description = "Enable public ingress on port 8000" + type = bool + default = true // or false depending on your needs +} + +variable "enable_auth" { + description = "Enable authentication" + type = bool + default = true // or false depending on your needs +} + +variable "auth_type" { + description = "Authentication type" + type = string + default = "token" // or basic depending on your needs + validation { + condition = contains(["basic", "token"], var.auth_type) + error_message = "The auth type must be either basic or token" + } +} + +resource "random_password" "chroma_password" { + length = 16 + special = true + lower = true + upper = true +} + +resource "random_password" "chroma_token" { + length = 32 + special = false + lower = true + upper = true +} + + +locals { + basic_auth_credentials = { + username = "chroma" + password = random_password.chroma_password.result + } + token_auth_credentials = { + token = random_password.chroma_token.result + } + tags = [ + "chroma", + "release-${replace(var.chroma_release, ".", "")}", + ] +} + +variable "ssh_public_key" { + description = "SSH Public Key" + type = string + default = "./chroma-do.pub" +} +variable "ssh_private_key" { + description = "SSH Private Key" + type = string + default = "./chroma-do" +} + +variable "chroma_data_volume_size" { + description = "EBS Volume Size of the attached data volume where your chroma data is stored" + type = number + default = 20 +} + + +variable "chroma_port" { + default = "8000" + description = "The port that chroma listens on" + type = string +} + +variable "source_ranges" { + default = ["0.0.0.0/0", "::/0"] + type = list(string) + description = "List of CIDR ranges to allow through the firewall" +} + +variable "mgmt_source_ranges" { + default = ["0.0.0.0/0", "::/0"] + type = list(string) + description = "List of CIDR ranges to allow for management of the Chroma instance. This is used for SSH incoming traffic filtering" +}