Intended deployment architecture

The Content Cache charm manages an nginx instance that caches responses from a set of backends. It does not know how to discover backends, route by hostname or path, or terminate client-facing TLS on its own. It also does not cache personalized or session-dependent content — see Charm design and how it works under the hood for the static-only caching assumption. Those responsibilities are delegated to other charms over relations.

Content Cache cannot be deployed by itself. It has no charm configuration options of its own — every behavior is supplied entirely through the cache-config relation. Deployed alone, with no related charm providing that relation, the unit has no backends configured: nginx serves no cache locations, and the unit sits in blocked status, waiting for a configuration-providing charm to be integrated.

This page describes the components required for a working deployment, the relations the charm supports, and how combining them enables or restricts specific use cases.

Required components

A working deployment needs at least two charms:

  1. Content Cache — this charm. Runs nginx and performs the actual caching. Optionally terminates incoming TLS.

  2. A backend-configuration charm, providing the cache-config relation. Exactly one of:

    • Content Cache Backends Config — a subordinate charm deployed onto the Content Cache unit. It provides a direct, minimal way to describe a set of backends with no routing or ingress features.

    • Ingress configurator — a principal charm that translates its own configuration into the same cache-config relation data, but requires a haproxy-route relation before publishing a usable backend configuration. Paired with HAProxy, it adds an ingress layer in front of Content Cache: hostname/path routing, TLS termination, DDoS protection, and health-check-based retries.

Everything else described below is optional and layers additional capability onto this minimal pair.

High-level deployment diagram

The diagram below shows Content Cache with every relation it supports. Solid arrows are the required, functional relation (cache-config); dashed arrows are optional or conditional integrations.

        flowchart TB
    BackendConfig["Content Cache Backends Config<br/>or Ingress configurator"] -->|"cache-config<br/>(required)"| CC["Content Cache"]
    CC -->|"proxied requests"| Backend["Backend / origin"]
    CertProvider["Certificate provider<br/>(e.g. self-signed-certificates, lego)"] -.->|"certificates<br/>(optional: terminate TLS here)"| CC
    CertProvider -.->|"receive-ca-cert<br/>(only for private/untrusted HTTPS backend CA)"| CC
    COS["grafana-agent"] -.->|"cos-agent<br/>(optional: metrics & logs)"| CC
    CC <-.->|"content-cache-peers<br/>(automatic, multi-unit port sync)"| CCPeer["Content Cache<br/>(other units)"]
    

Supported relations

Provides — relations where Content Cache offers data to a related charm:

Relation endpoint

Interface

Required?

Purpose

cache-config

content-cache-config

Yes

Backend addresses and per-backend cache/health-check settings. Without this relation the charm has nothing to cache.

cos-agent

cos_agent

Optional

Ship metrics/logs to a Canonical Observability Stack (COS) via grafana-agent or an equivalent principal charm.

Requires — relations where Content Cache consumes data from a related charm:

Relation endpoint

Interface

Required?

Purpose

certificates

tls-certificates

Optional

Terminate TLS for incoming connections directly at Content Cache — standalone, or as a second TLS hop behind HAProxy (see use cases below).

receive-ca-cert

certificate_transfer

Conditionally

Trust a CA so nginx can verify backend certificates. Required only if any configured backend uses an https:// URL; ignored entirely for HTTP-only backends.

Peers — coordination between units of this application:

Relation endpoint

Interface

Required?

Purpose

content-cache-peers

content-cache-peers

Automatic

Coordinates per-relation port allocation across all units of the same Content Cache application. Not user-configured.

How relations combine to support use cases

Each scenario below builds on the previous one by adding one more relation.

Scenario 1: direct backend caching

        flowchart LR
    CCBC["Content Cache Backends Config<br/>(subordinate)"] -->|"cache-config"| CC["Content Cache"]
    CC -->|"HTTP"| Backend["Backend / origin"]
    

The simplest supported deployment includes Content Cache Backends Config deployed as a subordinate directly onto the Content Cache unit and describes one set of backends over plain HTTP. There is no hostname/path-based routing and no TLS anywhere in the request path: each cache-config relation is served on its own dedicated port (see Charm design and how it works under the hood), and callers reach it directly at http://<content-cache-unit-ip>:<port>.

This pattern suits deployments with a small, fixed number of backend groups where clients (or an existing load balancer) can address Content Cache units and ports directly, and where routing decisions do not need to change dynamically. Content Cache’s own certificates relation can also be used here, without HAProxy, if callers need to reach Content Cache over HTTPS directly instead of plain HTTP — see How to connect to HTTPS backends.

Scenario 2: add an ingress with TLS termination at the front and a second TLS hop to Content Cache

        flowchart LR
    Client(["Client"]) -->|"🔒 HTTPS"| HAProxy["HAProxy"]
    HAProxy -->|"🔒 HTTPS"| CC["Content Cache"]
    CC -->|"HTTP"| Backend["Backend / origin"]
    IC["Ingress configurator"] -.->|"cache-config"| CC
    IC -.->|"haproxy-route"| HAProxy
    Lego["certificate provider<br/>charm"] -.->|"certificates"| HAProxy
    Lego -.->|"certificates"| CC
    Lego -.->|"send-ca-cert"| HAProxy
    

Content Cache Backends Config is replaced with Ingress configurator, which pairs with HAProxy over haproxy-route. A certificate provider such as lego integrates with HAProxy’s own certificates relation, so the client speaks HTTPS to HAProxy. The certificate provider charm also integrates directly with Content Cache’s own certificates relation, so Content Cache presents its own TLS certificate and listens with ssl on its allocated port; HAProxy then forwards over this second, independent HTTPS hop instead of plain HTTP, encrypting traffic for its entire path rather than only from the client to HAProxy. For HAProxy to trust this certificate, the same certificate provider also integrates with HAProxy over the certificate_transfer interface (send-ca-cert → receive-ca-certs); without it, HAProxy rejects Content Cache’s certificate and the hop fails. HAProxy adds hostname/path-based routing, load balancing across Content Cache units, and DDoS protection on top of what Scenario 1 provides. See How to connect to HTTPS backends and the tutorial for a full walkthrough of this deployment.

Scenario 3: add HTTPS to the backend

        flowchart LR
    Client(["Client"]) -->|"🔒 HTTPS"| HAProxy["HAProxy"]
    HAProxy -->|"🔒 HTTPS"| CC["Content Cache"]
    CC -->|"🔒 HTTPS"| Backend["Backend / origin"]
    IC["Ingress configurator"] -.->|"cache-config<br/>(backend-protocol=https)"| CC
    IC -.->|"haproxy-route"| HAProxy
    Lego["certificate provider<br/>charm"] -.->|"certificates"| HAProxy
    Lego -.->|"certificates"| CC
    Lego -.->|"send-ca-cert"| HAProxy
    Lego -.->|"receive-ca-cert"| CC

    %% Index 2: CC to Backend (Colors the line and the text)
    linkStyle 2 stroke:#d946ef,stroke-width:3px,color:#d946ef
    
    %% Index 3: IC to CC
    linkStyle 3 stroke:#d946ef,stroke-width:3px,color:#d946ef
    

Building on Scenario 2 (including its HAProxy-to-Content-Cache TLS hop and the matching send-ca-cert → receive-ca-certs trust relation), the backend is now addressed as an https:// URL. Content Cache must trust the backend’s CA to verify its certificate, so the certificate provider that issues the backend’s certificate also integrates over the receive-ca-cert relation directly with Content Cache. This is a fourth, independent TLS relation: the certificates issued for HAProxy’s client-facing listener and for Content Cache’s own listener do not need to share a CA with the one used to protect the backend. For simplicity, the diagram reuses the same certificate provider charm for all relations, but a separate certificate provider instance for the backend’s CA works the same way. Without receive-ca-cert, Content Cache still proxies to the backend, but nginx cannot verify its certificate against a trusted CA, so upstream TLS verification fails and requests to that backend return an error. See How to connect to HTTPS backends.

Observability

The cos-agent relation is additive to any of the scenarios above: integrating a grafana-agent (or equivalent) principal charm collects nginx metrics and logs into COS without changing how backends are configured. See How to enable COS.