---
title: Distributed cache
description: Configure a shared Valkey cache with IAM authentication and encrypted token storage using AWS KMS and Tink.
---
Single-instance deployments cache GitHub access tokens in process memory. With multiple instances, each independently requests tokens from GitHub, increasing API usage and latency. A shared [Valkey][valkey] cache eliminates this redundancy by allowing all instances to read and write from the same token store.

Chinmina supports Valkey (or any Redis-compatible server) as a distributed cache backend. When configured, Chinmina uses a multi-level caching strategy: a fast local in-memory cache backed by the shared Valkey instance.

> \[!CAUTION]
>
> Encryption is **strongly** recommended for any production distributed cache. Without it, GitHub access tokens are stored in plaintext in Valkey. A compromised cache instance would directly expose all cached credentials.

## As you go

Values to save for configuration are marked in the instructions below like this: `📝 ENV_VAR_NAME`.

Collect these values as you proceed through the infrastructure setup sections. They are used in the "Configure Chinmina" steps that follow each section.

## Create the Valkey instance

1. Create an ElastiCache Serverless Valkey cache (or replication group) by
   following the [ElastiCache getting started guide][elasticache-create]. Place
   the cache in the same VPC as your Chinmina instances, or ensure network
   connectivity via VPC peering or security group rules.

   Save the cache endpoint as `📝 VALKEY_ADDRESS` and cache name
   (replication group ID or serverless cache name) as

   `📝 VALKEY_IAM_CACHE_NAME`.

2. Enable in-transit encryption (TLS) on the cache. TLS is required for IAM
   authentication and is strongly recommended in all cases.

3. Create an [ElastiCache IAM-enabled user][elasticache-iam-auth] for Chinmina
   to authenticate with.

   Save the user ID as `📝 VALKEY_USERNAME`.

## Configure Chinmina for the Valkey cache

1. Set `CACHE_TYPE=valkey` and `VALKEY_ADDRESS` to the cache endpoint in
   `host:port` format:

   ```bash
   CACHE_TYPE=valkey
   VALKEY_ADDRESS=chinmina-cache-abcdef.serverless.use1.cache.amazonaws.com:6379
   ```

2. Enable IAM authentication and identify the cache:

   ```bash
   VALKEY_IAM_ENABLED=true
   VALKEY_IAM_CACHE_NAME=chinmina-cache
   VALKEY_IAM_SERVERLESS=true
   ```

   Set `VALKEY_IAM_SERVERLESS=true` for ElastiCache Serverless caches, or `false` for replication groups.

3. Set `VALKEY_USERNAME` to the IAM user ID created in the previous section:

   ```bash
   VALKEY_USERNAME=chinmina-cache-user
   ```

4. Grant the Chinmina execution role `elasticache:Connect` permission, scoped
   to the cache and user:

   ```json title="example-elasticache-iam-policy.json"
   {
     "Version": "2012-10-17",
     "Statement": [
       {
         "Effect": "Allow",
         "Action": "elasticache:Connect",
         "Resource": [
           "arn:aws:elasticache:us-east-1:123456789012:serverlesscache:chinmina-cache",
           "arn:aws:elasticache:us-east-1:123456789012:user:chinmina-cache-user"
         ]
       }
     ]
   }
   ```

> \[!NOTE]
>
> When IAM authentication is enabled, TLS is forced on regardless of the
> `VALKEY_TLS` setting. `VALKEY_PASSWORD` must be empty.

> \[!TIP]
>
> Chinmina uses the standard AWS SDK credential chain for IAM authentication.
> Credentials are typically supplied via IMDS (instance metadata service) or the
> container credential provider, consistent with the approach described in the
> [KMS guide](../kms.md).

## Configure cache encryption

Cache encryption uses [Tink][tink] for AEAD (Authenticated Encryption with Associated Data) with AES-256-GCM. An encrypted Tink keyset is stored in AWS Secrets Manager, protected by an AWS KMS envelope encryption key. At runtime, Chinmina decrypts the keyset and uses it to encrypt and decrypt cached tokens.

### Create the KMS key and Secrets Manager secret

1. Create a symmetric encryption KMS key (AES-256). Enable automatic annual
   rotation and set the deletion window to 30 days. Follow the
   [KMS key creation guide][kms-create-key] for detailed instructions.

   Save the key ARN or alias ARN as `📝 CACHE_ENCRYPTION_KMS_ENVELOPE_KEY_URI`.

2. Create an alias for the key, for example `alias/chinmina-cache-encryption`.
   A key alias simplifies key rotation and policy management. The alias ARN
   can be used anywhere a key ARN is accepted in the Chinmina configuration.

3. Update the key policy to allow the Chinmina execution role to decrypt:

   ```json title="example-kms-key-policy-statement.json" /arn:.*-role/
   {
     "Sid": "Allow Chinmina to decrypt the cache encryption keyset",
     "Effect": "Allow",
     "Principal": {
       "AWS": ["arn:aws:iam::123456789012:role/chinmina-task-role"]
     },
     "Action": ["kms:Decrypt", "kms:DescribeKey"],
     "Resource": "*"
   }
   ```

4. Create a Secrets Manager secret to hold the encrypted Tink keyset. Choose an
   encryption key for the secret value itself (this is separate from the KMS key
   used for the Tink keyset envelope encryption). Use a naming convention such
   as `/chinmina-bridge/{environment}/tink-keyset`:

   ```bash title="Create the Secrets Manager secret"
   aws secretsmanager create-secret \
     --name /chinmina-bridge/production/tink-keyset \
     --description "Encrypted Tink AEAD keyset for Chinmina cache encryption"
   ```

   Save the secret name or ARN as `📝 CACHE_ENCRYPTION_KEYSET_URI`.
   The keyset content will be uploaded in the next section.

5. Grant the Chinmina execution role access to read the secret:

   ```json title="example-secrets-manager-iam-policy.json"
   {
     "Version": "2012-10-17",
     "Statement": [
       {
         "Effect": "Allow",
         "Action": "secretsmanager:GetSecretValue",
         "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:/chinmina-bridge/*/tink-keyset-*"
       }
     ]
   }
   ```

   The trailing wildcard on the resource ARN accounts for the random suffix that
   Secrets Manager appends to secret names.

### Create and upload the Tink keyset

1. Install the `tinkey` CLI by following the [Tink installation
   instructions][tinkey-install].

2. Generate an encrypted AEAD keyset. The `--master-key-uri` flag encrypts the
   keyset with the KMS key during creation — no plaintext keyset is written to
   disk. The URI can reference a key alias or a full key ARN:

   ```bash title="Generate an encrypted Tink keyset" /aws-kms:\S+/
   tinkey create-keyset \
     --key-template AES256_GCM \
     --out-format json \
     --out encrypted-keyset.json \
     --master-key-uri aws-kms://arn:aws:kms:us-east-1:123456789012:alias/chinmina-cache-encryption
   ```

3. Upload the encrypted keyset to the Secrets Manager secret created in the
   previous section. The secret must already exist — `put-secret-value` sets the
   value of an existing secret, it does not create one:

   ```bash title="Upload the encrypted keyset"
   aws secretsmanager put-secret-value \
     --secret-id /chinmina-bridge/production/tink-keyset \
     --secret-string file://encrypted-keyset.json
   ```

   Delete the local encrypted keyset file after uploading.

### Configure Chinmina to use the keyset

1. Enable cache encryption:

   ```bash
   CACHE_ENCRYPTION_ENABLED=true
   ```

2. Set the Secrets Manager URI for the keyset. The `aws-secretsmanager://`
   prefix is stripped, and the remainder is passed directly as the `SecretId` to
   the GetSecretValue API. Since that API accepts either a secret name or a full
   ARN, both formats work:

   ```bash title="Using the secret name"
   CACHE_ENCRYPTION_KEYSET_URI=aws-secretsmanager:///chinmina-bridge/production/tink-keyset
   ```

   ```bash title="Using the full ARN"
   CACHE_ENCRYPTION_KEYSET_URI=aws-secretsmanager://arn:aws:secretsmanager:us-east-1:123456789012:secret:/chinmina-bridge/production/tink-keyset-Ab1CdE
   ```

   The name form is simpler and sufficient unless you need to reference a secret
   in a different account or region.

3. Set the KMS key URI for envelope decryption. Use the `aws-kms://` scheme
   followed by the key ARN or alias ARN:

   ```bash /aws-kms:\S+/
   CACHE_ENCRYPTION_KMS_ENVELOPE_KEY_URI=aws-kms://arn:aws:kms:us-east-1:123456789012:alias/chinmina-cache-encryption
   ```

4. On startup, Chinmina performs a test encrypt/decrypt cycle. If the keyset or
   KMS key is misconfigured, the service will fail to start with a clear error
   message.

> \[!NOTE]
>
> The keyset is automatically refreshed from Secrets Manager every 15 minutes. If a refresh fails, the service continues with the current in-memory keyset and logs the error. This enables zero-downtime key rotation.

## Key rotation

Tink keysets hold multiple keys simultaneously. Only the *primary* key encrypts, but all keys decrypt. This allows zero-downtime rotation: add a new key, wait for all instances to load it, then promote to primary.

Chinmina refreshes its keyset from Secrets Manager every 15 minutes. The rotation procedure accounts for this interval and the cache TTL (tokens expire after 45 minutes).

### Timeline

```text
T+0min    Stage 1 — Upload keyset with new key (non-primary)
T+15min   All instances can decrypt with both keys
T+75min   Stage 2 — Promote new key to primary
T+90min   All instances encrypting with new key
T+150min  Stage 3 (optional) — Disable old key
```

### Stage 1: Add a new key

1. Download the current encrypted keyset from Secrets Manager:

   ```bash title="Download the current keyset"
   aws secretsmanager get-secret-value \
     --secret-id /chinmina-bridge/production/tink-keyset \
     --query SecretString --output text > current-keyset.json
   ```

2. Add a new key to the keyset. The `add-key` command adds a new key without promoting it to primary — the existing primary key continues to be used for encryption:

   ```bash title="Add a new key to the keyset" /aws-kms:\S+/
   tinkey add-key \
     --in current-keyset.json \
     --out stage1-keyset.json \
     --key-template AES256_GCM \
     --master-key-uri aws-kms://arn:aws:kms:REGION:ACCOUNT:key/KEY-ID
   ```

3. Upload the updated keyset:

   ```bash title="Upload the keyset with the new key"
   aws secretsmanager update-secret \
     --secret-id /chinmina-bridge/production/tink-keyset \
     --secret-string file://stage1-keyset.json
   ```

4. Wait at least 15 minutes for all instances to refresh. After this point, all instances can decrypt with both keys.

### Stage 2: Promote the new key

Wait an additional 60 minutes after stage 1 completes (75 minutes from the start). This ensures all tokens encrypted with the old primary key have expired from the cache.

1. Promote the new key to primary. Find the new key's ID from the keyset metadata:

   ```bash title="Promote the new key to primary" /aws-kms:\S+/
   tinkey promote-key \
     --in stage1-keyset.json \
     --out stage2-keyset.json \
     --key-id <NEW_KEY_ID> \
     --master-key-uri aws-kms://arn:aws:kms:REGION:ACCOUNT:key/KEY-ID
   ```

2. Upload the updated keyset:

   ```bash title="Upload the keyset with the promoted key"
   aws secretsmanager update-secret \
     --secret-id /chinmina-bridge/production/tink-keyset \
     --secret-string file://stage2-keyset.json
   ```

3. After another 15 minutes, all instances will encrypt new tokens with the new primary key.

### Stage 3: Disable the old key (optional)

After another cache TTL cycle (at least 60 minutes after stage 2), all tokens encrypted with the old key have expired. The old key can be safely disabled.

```bash title="Disable the old key" /aws-kms:\S+/
tinkey disable-key \
  --in stage2-keyset.json \
  --out final-keyset.json \
  --key-id <OLD_KEY_ID> \
  --master-key-uri aws-kms://arn:aws:kms:REGION:ACCOUNT:key/KEY-ID
```

Upload the final keyset using the same `aws secretsmanager update-secret` command. Delete any local keyset files after uploading.

> \[!CAUTION]
>
> Do not disable the old key before the cache TTL has elapsed. Tokens encrypted with the old key will fail to decrypt, resulting in cache misses and increased GitHub API traffic.

[valkey]: https://valkey.io/

[elasticache-create]: https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/GettingStarted.html

[elasticache-iam-auth]: https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/auth-iam.html

[kms-create-key]: https://docs.aws.amazon.com/kms/latest/developerguide/create-keys.html

[tinkey-install]: https://developers.google.com/tink/install-tinkey

[tink]: https://developers.google.com/tink
