Aproxy Subordinate Charm
| Channel | Revision | Published | Runs on |
|---|---|---|---|
| latest/stable | 7 | 17 Oct 2025 | |
| latest/stable | 4 | 14 Oct 2025 | |
| latest/edge | 10 | 27 Oct 2025 | |
| latest/edge | 9 | 27 Oct 2025 |
juju deploy aproxy
Deploy universal operators easily with Juju, the Universal Operator Lifecycle Manager.
Platform:
Charm architecture
At its core, the aproxy subordinate charm installs and manages the aproxy snap, configures it to forward intercepted TCP traffic to a target proxy, and manages nftables rules to transparently redirect outbound traffic through aproxy.
The charm design is subordinate, meaning it attaches to a principal application (such as a workload needing controlled egress traffic). Unlike a sidecar charm, this subordinate runs directly on the same machine as the principal charm. It does not use Pebble or sidecar containers, because it manages system-level services (snap and nftables) instead of container workloads.
The charm relies on:
- Snap service management for the aproxy snap.
- nftables rules dynamically configured by the charm to enforce transparent proxy.
As a result, if you run juju status in a model where the aproxy charm is deployed, you’ll see something like:
Unit Workload Agent Machine Public address Ports Message
aproxy/0* active idle 0 10.0.0.5 Service ready on target proxy proxy.address:80.
This shows that aproxy runs on the same unit as the principal charm.
High-level overview of aproxy deployment
The following diagram shows a typical deployment of the aproxy subordinate charm:
C4Context
title System Context diagram for aproxy subordinate charm
Person(dev, "Developer / Operator", "Deploys and manages applications with Juju")
System_Ext(proxy, "Upstream Proxy", "Trusted proxy server that receives forwarded HTTP/HTTPS traffic")
System_Boundary(b0, "Host VM or Container") {
System(principal, "Principal Application", "e.g., web app, API service, or database")
System(aproxy, "aproxy Subordinate Charm", "Intercepts outbound traffic via nftables and forwards to upstream proxy")
}
Rel(dev, principal, "Deploys and configures via Juju")
Rel(principal, aproxy, "Co-locates and intercepts outbound traffic")
Rel(aproxy, proxy, "Forwards proxied traffic")
UpdateRelStyle(dev, principal, $offsetY="-20", $offsetX="5")
UpdateRelStyle(principal, aproxy, $offsetY="30", $offsetX="-20")
UpdateRelStyle(aproxy, proxy, $offsetX="10")
-
The principal application generates outbound TCP traffic.
-
The aproxy subordinate charm intercepts this traffic via nftables and routes it through the aproxy snap.
-
The traffic is forwarded to an external target proxy server (configured via
proxy-address).
Charm architecture
The following diagram shows the architecture of the aproxy charm:
C4Component
title Component diagram for aproxy subordinate charm
UpdateLayoutConfig($c4ShapeInRow="2", $c4BoundaryInRow="2")
System_Boundary(b1, "aproxy Subordinate Charm (machine charm)") {
Component(charm, "Charm logic", "Python (ops framework)", "Handles Juju events and manages system state")
Component(snap, "aproxy snap", "Snap package", "Provides local proxy listener on 127.0.0.1:8443")
Component(nft, "nftables rules", "nftables", "Redirects outbound traffic to the aproxy listener")
}
System_Ext(upstream, "Target Proxy Server", "External proxy configured via charm settings")
Rel(charm, snap, "Installs and configures")
Rel(charm, nft, "Applies rules")
Rel(nft, snap, "Intercept TCP connections")
Rel(snap, upstream, "Forwards traffic to upstream proxy")
UpdateRelStyle(charm, snap, $offsetY="-20", $offsetX="-40")
UpdateRelStyle(charm, nft, $offsetX="10")
UpdateRelStyle(snap, upstream, $offsetY="-30", $offsetX="10")
-
The charm code (
charm.py) observes Juju lifecycle events and configures both the snap and nftables. -
The aproxy snap provides the actual proxy functionality, listening on
127.0.0.1:8443. -
nftables rules transparently intercept outbound traffic on configured ports and redirect it to the snap.
Containers
This subordinate charm does not use containers or Pebble-managed processes. Instead, it directly manages system resources (snap and nftables) on the host machine.
Metrics
To be added in the future.
Juju events
The charm observes the following Juju events:
-
install: Installs the aproxy snap. -
start: Configures nftables rules and ensures interception is running. -
config-changed: Reapplies configuration (proxy address, excluded addresses list, intercepted ports). -
stop: Cleans up nftables rules and removes the snap.
See more in the Juju docs: Hook
Charm code overview
The src/charm.py is the default entry point for a charm and has the AproxyCharm Python class which inherits
from CharmBase. CharmBase is the base class from which all charms are formed, defined
by Ops (Python framework for developing charms).
See more in the Juju docs: Charm
This charm uses a holistic event handling approach to manage installation, configuration, and lifecycle events through a unified handler. Rather than maintaining separate methods for each Juju event, the charm consolidates related logic into a single configuration flow to ensure consistency and idempotency across charm operations.
In the __init__ method, the charm observes key Juju lifecycle events and maps them to corresponding handlers:
-
install,start, andconfig-changedevent →_on_start_and_configure: Handles snap installation, snap configuration, and nftables setup tasks in a unified process. -
stopevent →_on_stop: Handles nftables cleanup, and snap removal.
For example, when a configuration changes:
- User runs:
juju config aproxy proxy-address=my-proxy.local
-
A
config-changedevent is emitted. -
The charm observes it:
self.framework.observe(self.on.config_changed, self._on_start_and_configure)
_on_start_and_configurevalidates the configuration, sets snap options, and reapplies the nftables rules.