12-Factor Web Development: Support for Valkey

Web App Development: Support for Valkey

Hi everyone,

We would like to announce that we have added support for Valkey integration for all supported frameworks (Flask, Django, FastAPI, Go, Express.js, and Spring Boot)!

Valkey is a high-performance key-value data store, created as a drop-in alternative to Redis following Redis’s license change. With this new integration, we support the valkey_client interface, which is provided by the Valkey charm. Below is an example of how you can charm a simple Flask app to integrate with the Valkey charm for key-value storage.

Valkey Flask Application

In this post, we will see how we can charm a Flask app to integrate it with the Valkey charm to store and retrieve data. We will start by setting up our development environment:

multipass launch --cpus 4 --disk 50G --memory 4G --name charm-dev 24.04
multipass shell charm-dev

sudo snap install concierge --classic
sudo concierge prepare -p dev

juju add-model valkey-demo

Now that we have our environment ready, let’s deploy Valkey:

juju deploy valkey --channel 9/edge --trust

Now that we have Valkey deploying in the background, we can start writing the Flask application.

First, create the folder for our application:

mkdir flask-valkey
cd flask-valkey

Let’s create the app.py file. As valkey is compatible with both the valkey library and the redis library, we will provide an example for each library. Navigate to the section for the library that you prefer:

Creating app.py using the valkey library

import os
import valkey
from flask import Flask

app = Flask(__name__)

def get_valkey_uri():
    return os.environ.get("VALKEY_DB_CONNECT_STRING", "")

@app.route("/set/<key>/<value>")
def set_key(key, value):
    uri = get_valkey_uri()
    if not uri:
        return "Valkey not configured", 503
    v = valkey.Valkey.from_url(uri)
    v.set(key, value)
    return "OK"

@app.route("/get/<key>")
def get_key(key):
    uri = get_valkey_uri()
    if not uri:
        return "Valkey not configured", 503
    v = valkey.Valkey.from_url(uri)
    return v.get(key) or ""

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=os.environ.get("APP_PORT", "8080"))

Then, create a requirements.txt file:

flask
valkey

Creating app.py using the redis library

import os
import redis
from flask import Flask

app = Flask(__name__)

def get_valkey_uri():
    return os.environ.get("VALKEY_DB_CONNECT_STRING", "")

@app.route("/set/<key>/<value>")
def set_key(key, value):
    uri = get_valkey_uri()
    if not uri:
        return "Database not configured", 503
    database = redis.Redis.from_url(uri)
    database.set(key, value)
    return "OK"

@app.route("/get/<key>")
def get_key(key):
    uri = get_valkey_uri()
    if not uri:
        return "Database not configured", 503
    database = redis.Redis.from_url(uri)
    return database.get(key) or ""

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=os.environ.get("APP_PORT", "8080"))

Then, create a requirements.txt file:

flask
redis

Now that we have our app created, we need to create the OCI image for the Flask application:

# Deploy a local registry in the cluster
curl https://raw.githubusercontent.com/canonical/spring-petclinic/refs/heads/resources/registry.yaml | kubectl apply -f -

rockcraft init --profile flask-framework --name flask-valkey
rockcraft pack

# Push image to local registry
sudo rockcraft.skopeo --insecure-policy copy oci-archive:flask-valkey_0.1_amd64.rock docker://localhost:5000/flask-valkey:0.1 --dest-tls-verify=false

Once we have the OCI Image pushed to the local registry, we can create the charm:

mkdir charm
cd charm

charmcraft init --profile flask-framework --name flask-valkey

We need to modify the charmcraft.yaml file to add the Valkey relation. Add the following lines to the requires section:

requires:
  valkey:
    interface: valkey_client
    optional: false
    limit: 1

We also need to update the requirements.txt file generated by charmcraft init to use ops >= 3 to be compatible with the latest version of paas-charm

ops >= 3
paas-charm>=1.0,<2

Note: This manual step will no longer be necessary once we provide the relevant fix to charmcraft on the stable track

Then, we can continue with creating the charm:

charmcraft pack

Once the charm is created, we can deploy it to our model:

# Deploy the charm
juju deploy ./flask-valkey_amd64.charm --resource flask-app-image=localhost:5000/flask-valkey:0.1

# Integrate with Valkey
juju integrate flask-valkey:valkey valkey:valkey-client

Now that our charm is deployed and integrated, we can test the Valkey integration:

# Get the flask-valkey container ip
UNIT_IP=$(juju show-unit flask-valkey/0 | yq '.flask-valkey/0.address')

# Set a key-value pair
curl $UNIT_IP:8000/set/mykey/myvalue

# Get the value back
curl $UNIT_IP:8000/get/mykey
# Expected output: myvalue

That’s all it takes to enable Valkey integration for our webapp! We have deployed the Flask application and integrated it with the Valkey charm.


Environment Variables Reference

When integrated with Valkey, your application will receive the following environment variables:

Variable Description
VALKEY_DB_CONNECT_STRING Full connection URL with embedded credentials
VALKEY_DB_SCHEME URL scheme (e.g., “valkey”)
VALKEY_DB_NETLOC Network location part of the URL
VALKEY_DB_PATH URL path component
VALKEY_DB_PARAMS URL parameters
VALKEY_DB_QUERY URL query string
VALKEY_DB_FRAGMENT URL fragment
VALKEY_DB_USERNAME Username for authentication
VALKEY_DB_PASSWORD Password for authentication
VALKEY_DB_HOSTNAME Valkey server hostname
VALKEY_DB_PORT Valkey server port
VALKEY_DB_NAME Database name/index
VALKEY_DB_SENTINEL_ENDPOINTS A comma-separated list of Valkey’s sentinel endpoints
VALKEY_DB_READ_ONLY_ENDPOINTS A comma-separated list of Valkey’s read-only endpoints
VALKEY_MODE Valkey mode (e.g., “sentinel”)
VALKEY_VERSION Valkey server version
2 Likes