Skip to content

Redis

AppMecca provides a shared, managed Redis cache your application can use for caching, session storage, and task queues. Enabling it is a single line in appmecca.yaml, and AppMecca injects the connection URL into your containers automatically.

redis:
  enabled: true

This block used to be called cache:

It was renamed to redis: because cache: also names the per-service deploy[].cache: block, which configures HTTP edge caching and has nothing to do with Redis. One word for two subsystems caused real confusion — including inside AppMecca's own engineering — so the three concerns now have three names:

Block Where What it is
redis: config root your managed Redis datastore (REDIS_URL)
cdn: config root how far your CDN reach extends
cache: per service edge caching policy — see Content Caching

A leftover cache: at the config root is refused with a message naming the replacement, rather than being silently ignored — ignoring it would deploy your app without REDIS_URL.

Putting cache: at the wrong nesting level is silently ignored — no error, no warning, and no Redis.

Enabling the Cache

Add this to your appmecca.yaml, at the top level (not inside a service):

redis:
  enabled: true

enabled: true is required — a bare cache: {} block leaves the cache off.

On the next deployment, AppMecca will inject Redis connection details into all of your containers as environment variables.

Injected Environment Variables

When cache.enabled: true, AppMecca injects these 2 environment variables into every container:

Variable Example Value Description
REDIS_URL redis://cache-host.internal:6379/0 Redis connection URL
CELERY_BROKER_URL redis://cache-host.internal:6379/0 Same URL, for Celery compatibility

Both variables contain the same URL. CELERY_BROKER_URL is provided as a convenience because Celery looks for it by default. Read them from the environment — never hard-code the host.

Shared cache

The Redis instance is shared across all applications and all branch deployments in the same environment. Each app and branch connects to the same Redis server. AppMecca injects a CACHE_PREFIX system variable (e.g., myapp:main: or myapp:feature-payments:) to isolate cache keys between branches. Use this prefix in your application code (see examples below).

Flask Example

Caching with Flask-Caching

requirements.txt:

flask
flask-caching
redis

app.py:

import os
from flask import Flask
from flask_caching import Cache

app = Flask(__name__)

# Configure Redis cache from APPMECCA-injected REDIS_URL
app.config["CACHE_TYPE"] = "RedisCache"
app.config["CACHE_REDIS_URL"] = os.environ.get("REDIS_URL")
# CACHE_PREFIX isolates keys between apps AND branches (e.g., "myapp:main:")
app.config["CACHE_KEY_PREFIX"] = os.environ.get("CACHE_PREFIX", "")

cache = Cache(app)

@app.route("/")
@cache.cached(timeout=60)
def index():
    return "This response is cached for 60 seconds."

Key prefixing

Because the Redis instance is shared across apps and branches, always use the CACHE_PREFIX system variable to prefix your cache keys. This variable includes both the app name and the sanitised branch name (e.g., myapp:feature-payments:), ensuring complete isolation between branches.

Session Storage with Flask-Session

import os
from flask import Flask, session
from flask_session import Session

app = Flask(__name__)

app.config["SESSION_TYPE"] = "redis"
app.config["SESSION_REDIS"] = os.environ.get("REDIS_URL")
app.config["SESSION_KEY_PREFIX"] = f"{os.environ.get('CACHE_PREFIX', '')}session:"

Session(app)

Django Example

Caching with django-redis

settings/production.py:

import os

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": os.environ["REDIS_URL"],
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        },
        # CACHE_PREFIX isolates keys between apps AND branches
        "KEY_PREFIX": os.environ.get("CACHE_PREFIX", ""),
    }
}

Session Storage

# In settings/production.py, after CACHES config above:

SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"

Celery Task Queue

This is the most common use case for Redis in Django projects: running background tasks with Celery.

appmecca.yaml:

containers:
  - name: myapp
    dockerfile: Dockerfile
    context: .

deploy:
  - name: web
    container: myapp
    port: 8000
    command: "gunicorn myapp.wsgi:application -w 4 -b 0.0.0.0:8000"
    health_check:
      path: /health
    environment:
      DJANGO_SETTINGS_MODULE: myapp.settings.production

  - name: worker
    container: myapp
    command: "celery -A myapp worker -l info -c 4"

  - name: beat
    container: myapp
    command: "celery -A myapp beat -l info"

database:
  enabled: true

redis:
  enabled: true

myapp/celery.py:

import os
from celery import Celery

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myapp.settings.production")

app = Celery("myapp")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()

settings/production.py:

import os

# Celery reads CELERY_BROKER_URL automatically when configured this way
CELERY_BROKER_URL = os.environ.get("CELERY_BROKER_URL")
CELERY_RESULT_BACKEND = os.environ.get("REDIS_URL")
CELERY_ACCEPT_CONTENT = ["json"]
CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json"

Workers don't need a port

Notice that the worker and beat services in the example above have no port field. They are internal services that process tasks from the Redis queue. Only the web service needs a port because it handles HTTP traffic.

Node.js Example

Caching with ioredis

import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL);
// CACHE_PREFIX isolates keys between apps AND branches (e.g., "myapp:main:")
const PREFIX = process.env.CACHE_PREFIX || "";

// Set a value
await redis.set(`${PREFIX}user:123`, JSON.stringify(user), "EX", 3600);

// Get a value
const cached = await redis.get(`${PREFIX}user:123`);

Session Storage with Express

import session from "express-session";
import RedisStore from "connect-redis";
import Redis from "ioredis";

const redisClient = new Redis(process.env.REDIS_URL);

app.use(
  session({
    store: new RedisStore({
      client: redisClient,
      prefix: `${process.env.CACHE_PREFIX || ""}session:`,
    }),
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
  })
);

BullMQ Task Queue

import { Queue, Worker } from "bullmq";
import Redis from "ioredis";

const connection = new Redis(process.env.REDIS_URL);

// Create a queue
const emailQueue = new Queue("email", { connection });

// Add a job
await emailQueue.add("send-welcome", { userId: 123 });

// Process jobs (in a worker service)
const worker = new Worker(
  "email",
  async (job) => {
    await sendWelcomeEmail(job.data.userId);
  },
  { connection }
);

Using the Redis URL Template

If your application expects a different environment variable name for the Redis URL, you can use the ${redis_url} template in your appmecca.yaml:

deploy:
  - name: web
    container: myapp
    port: 8000
    environment:
      MY_CACHE_URL: "${redis_url}"
      QUEUE_BACKEND: "${redis_url}"

redis:
  enabled: true

The ${redis_url} template resolves to the same value as the REDIS_URL variable. See Environment Variables for the full list of available templates.

Rotating the Cache Password

On an environment where REDIS_URL carries a per-app password (rediss://tnt2-…@…), mecca app rotate-credentials replaces it — alone with --store cache, or together with the database password by default:

mecca app rotate-credentials myapp -b main --store cache --wait

When it completes ElastiCache refuses the old password, and AppMecca redeploys the release the branch is running so your containers get the new one. ElastiCache can take several minutes to apply a password change. If it is still applying when the command finishes, the cache line reads rotated, still applying: the old password is refused once ElastiCache finishes, and there is nothing to re-run. Where REDIS_URL carries no password at all, there is nothing to rotate and the result says skipped. Rotation needs the ADMINISTRATOR or OWNER role in the app's group and a signed-in session. See Rotating Credentials for what each outcome means.

Combining Database and Cache

Most production applications need both a database and a cache. Here is a complete example:

containers:
  - name: myapp
    dockerfile: Dockerfile
    context: .

deploy:
  - name: web
    container: myapp
    port: 8000
    health_check:
      path: /health

  - name: worker
    container: myapp
    command: "celery -A myapp worker -l info"

database:
  enabled: true

redis:
  enabled: true

With this configuration, every container receives all 8 variables:

Variable Source
DATABASE_URL Database
DB_HOST Database
DB_PORT Database
DB_NAME Database
DB_USER Database
DB_PASSWORD Database
REDIS_URL Cache
CELERY_BROKER_URL Cache

Next Steps