Orrery Studio Part 6: Self-Hosting AI Agents with Docker Compose

Self-hosting Orrery Studio, with a remote A2A agent and an MCP egress proxy, took four new pieces in a Docker Compose file: a demo A2A agent, a one-shot job that makes its TLS certificates, an egress...

Status, October 2026: the Studio runs in the Tirraflow lab for admins only, on Orrery 0.10.0. One security question below is still open, and it's described as such.

Self-hosting Orrery Studio, with a remote A2A agent and an MCP egress proxy, took four new pieces in a Docker Compose file: a demo A2A agent, a one-shot job that makes its TLS certificates, an egress proxy rule and two nginx routes. This part shows the setup on Tirraflow's single VPS, and the five problems found in its first day: one hit production, four were caught in review.


The Setup

Orrery Studio deployment on one VPS with Docker Compose: nginx in front, Orrery and the demo A2A writer on internal networks, a one-shot certificate job, and a squid egress proxy as the only way out to two hosts
orrery:
  environment:
    ORRERY_STUDIO_ENABLED: "true"
    ORRERY_STUDIO_REGISTRY: /studio/registry.yaml
    ORRERY_STUDIO_EGRESS_PROXY: http://lab-egress:3128
    ORRERY_A2A_PRIVATE_ALLOW: demo-writer.studio.internal
    ORRERY_A2A_CA_FILE: /ca/ca.pem
  volumes:
    - ./labs/orrery/studio-registry.yaml:/studio/registry.yaml:ro
    - studio-demo-ca:/ca:ro     # the CA certificate only

The demo writer and the certificate job ship inside the signed Orrery image, so the Studio's demo pieces run only released, signature-verified code.


Five Problems From the First Day

1. The writer was killed before it could listen (production)

The writer has no LLM, so 128 MiB of memory seemed plenty. It went into an out-of-memory loop: importing the a2a-sdk and uvicorn stack takes about 137 MiB steady and 147 MiB at peak. The limit is now 256 MiB, with the measurement written next to it. Measure, then set the limit.

2. A certificate renewal could stop half-way (review)

The job runs as root only to write into the volumes and hand files to the image's user. With just the CHOWN capability, the first run worked, but a renewal would write a new ca.pem, then fail to open the writer's existing certificate. That leaves a new CA beside an old certificate, and the lab stays broken until someone deletes the volumes. It now also gets DAC_OVERRIDE and FOWNER, the Linux capabilities to open and change files it doesn't own, and still nothing else.

3. Both sides read certificates only at start (review)

Orrery loads the CA once, and the writer loads its certificate once. A renewal that restarts only one of them pairs a new CA with an old certificate. So the lab's deploy job watches the certificate job's output, and when it says regenerating, it restarts the writer and Orrery together.

if compose logs --no-log-prefix --since "$STARTED" lab-studio-certs | grep -q '^regenerating'; then
  compose restart lab-studio-writer orrery
fi

4. A 30-day certificate nobody would renew (review)

The job's default was 30 days, renewed when a week is left. But it only runs on a lab deploy, and nothing schedules one. The lab would have broken quietly a month later. Since this CA is trusted by one Orrery and nothing else, the certificates now last 825 days.

5. nginx would buffer anyone's upload (review)

A composition can be bigger than the lab's usual 64 KB limit, so /studio/ needed a larger one. But a buffering nginx reads the whole body before Orrery can reject an unauthenticated caller, so anyone could make it hold that much memory at a time. The body is now streamed straight through, with a 1 MB cap. The live trace gets its own route with buffering off and a 10-minute timeout.

location /studio/ {
    client_max_body_size    1m;
    proxy_request_buffering off;   # Orrery refuses strangers before the body is read
}

Still Open: The Proxy Checks Names, Not SNI

Squid allows a CONNECT by the hostname in the request, and the -n flag stops it from reverse-resolving bare IP addresses. But it can't see the SNI, the hostname sent inside the TLS handshake. tirraflow.com sits behind Cloudflare's shared addresses, so code inside Orrery that set its own SNI could reach any Cloudflare-fronted site through that tunnel.

Orrery's own clients send the real hostname, so this needs hostile code already running in the container. Closing it fully means a squid build that can peek at the SNI, or a route to the blog's origin that never leaves the box. That decision is still open, and the squid config says so in a comment.


Where This Leaves the Studio

To try the parts that are public today, connect your own assistant to https://tirraflow.com/mcp, the same MCP server the Studio's research node uses. Then start the series again from Part 1, or browse the hands-on labs.

Read on Tirraflow