Skip to content

Commit fb839ff

Browse files
authored
[minor] Document portable deployment architecture (#14)
1 parent 32de952 commit fb839ff

12 files changed

Lines changed: 515 additions & 52 deletions

infrastructure/cloud-compose.mdx

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,30 @@ Prefer the module for the provider the caller already selected:
7878

7979
| Entry point | Modular source pattern | Infrastructure boundary | Identity and secrets |
8080
| --- | --- | --- | --- |
81-
| Google Cloud | `github.com/libops/cloud-compose//providers/gcp?ref=RELEASE_OR_FULL_SHA` | Compute, network, firewall, persistent disks, service accounts, and optional GCP integrations | Can use a consumer-configured Vault GCP IAM role; the operator owns the role, policy, and secret paths |
81+
| Google Cloud, new deployment | `github.com/libops/cloud-compose//providers/gcp?ref=RELEASE_OR_FULL_SHA` | Compute, network, firewall, persistent disks, service accounts, and optional GCP integrations | Can use a consumer-configured Vault GCP IAM role; the operator owns the role, policy, and secret paths |
82+
| Google Cloud, 1.x-compatible root | `github.com/libops/cloud-compose?ref=RELEASE_OR_FULL_SHA` | The same GCP boundary while preserving the historical indexed `module.gcp[0]` address | The root accepts only `cloud_provider = "gcp"` and has the same GCP identity boundary |
8283
| DigitalOcean | `github.com/libops/cloud-compose//providers/do?ref=RELEASE_OR_FULL_SHA` | Droplet, network/firewall inputs, and persistent volumes exposed by the module | Consumer-managed provider token and secret authentication |
8384
| Linode | `github.com/libops/cloud-compose//providers/linode?ref=RELEASE_OR_FULL_SHA` | Instance, firewall inputs, and persistent volumes exposed by the module | Consumer-managed provider token and secret authentication |
8485
| Ansible | `ansible/` role from the pinned release | Bootstrap files and packages on an existing Debian or Ubuntu host | The existing-host owner supplies OS access, secrets, network, DNS, firewall, and storage |
8586
| Salt | `salt/` formula from the pinned release | Bootstrap files and packages on an existing Debian or Ubuntu host | The existing-host owner supplies OS access, secrets, network, DNS, firewall, and storage |
8687

87-
Pin the source to the reviewed release or a full commit. The compatibility dispatcher at the repository root remains available, but a new caller should use one provider entrypoint so Terraform does not load unrelated providers. Do not source production infrastructure from a moving branch.
88+
Pin the source to the reviewed release or a full commit. Since `1.3.0`, each
89+
entrypoint statically declares only its target provider. The repository root is
90+
not an all-provider dispatcher: it is a GCP-only compatibility entrypoint for
91+
existing GCP state and intentionally has no DigitalOcean or Linode dependency.
92+
New deployments should select one path under `providers/`. Do not source
93+
production infrastructure from a moving branch.
8894

8995
Provider entrypoints should normalize the same runtime object before calling provider-specific resources. A template preset supplies repository, plugin, and package defaults only; an explicit caller value wins. Shared host behavior belongs in provider-neutral runtime modules so Google Cloud, DigitalOcean, Linode, Ansible, and Salt do not grow independent application lifecycles. Each adapter must reject an input it cannot support rather than silently ignore it.
9096

91-
Changing an existing deployment from the root dispatcher to a provider module changes module addresses even when the real infrastructure is identical. Preserve the addresses with release-provided `moved` blocks or an explicit, reviewed state migration. Do not let Terraform destroy and recreate a data-bearing host to make the module path look cleaner.
97+
An existing GCP deployment can remain on the root source at `1.3.0` or later without
98+
loading unrelated providers or moving `module.gcp[0]`. Moving it to
99+
`providers/gcp` is an optional, separate state refactor. Existing DigitalOcean
100+
and Linode callers must adopt their provider-specific source and place the
101+
release-documented `moved` block in the caller's root module. In every case,
102+
back up remote state and require a plan with address moves only—no VM, firewall,
103+
or durable-volume replacement or deletion. Do not recreate a data-bearing host
104+
merely to make a module path look cleaner.
92105

93106
## Pin the complete runtime contract
94107

@@ -150,9 +163,9 @@ If the selected release does not contain that foundation module, follow its
150163
release-specific IAM instructions. Do not copy an unpublished foundation
151164
module from a development branch into an otherwise pinned production stack.
152165

153-
<Warning>
154-
In the [July 13, 2026 release-status snapshot](/infrastructure/current-release-status), PPB `0.5.0` exists as a source/GitHub release but its versioned container is not published. The power-management paragraphs below define the contract a selected release must satisfy; they do not make `0.5.0` a consumable image or make the managed shared-router/private-PPB path available.
155-
</Warning>
166+
<Info>
167+
The [July 17, 2026 release-status snapshot](/infrastructure/current-release-status) records cloud-compose `1.5.0` and the signed PPB `0.5.1` image as released self-hosted dependencies. That does not make the separate managed shared-router/private-PPB integration generally available; its API, edge, and end-to-end promotion gates remain independent.
168+
</Info>
156169

157170
The optional Cloud Run power-management ingress reaches the VM's private
158171
application port through [Direct VPC
@@ -166,10 +179,20 @@ Terraform identity needs authority to manage host-project and subnet IAM; the
166179
application Terraform identity needs authority to inspect the selected network
167180
and manage its per-stack firewall rule.
168181

182+
Do not assign the Direct VPC revision to a [Serverless VPC Access connector's
183+
subnet](https://cloud.google.com/vpc/docs/serverless-vpc-access#ip_address_ranges).
184+
A connector requires its own otherwise-unused `/28`, while Direct VPC
185+
requires capacity in the selected `/26` or larger application subnet. If an
186+
unrelated workload still needs a connector, give that connector a separate
187+
subnet and keep it outside this firewall source range unless it belongs in the
188+
same trust boundary.
189+
169190
The word "ingress" here describes the proxy's application role, not a Direct
170-
VPC ingress feature. Cloud Run services do not support Direct VPC ingress.
171-
Public requests enter Cloud Run through its configured service/edge ingress;
172-
Direct VPC egress is only the private hop from that service to the VM.
191+
VPC ingress feature. Cloud Run services and jobs do not support Direct VPC
192+
ingress; worker pools have a separate ingress capability that this architecture
193+
does not use. Public requests enter Cloud Run through its configured
194+
service/edge ingress; Direct VPC egress is only the private hop from that
195+
service to the VM.
173196

174197
Cloud-compose configures this Cloud Run attachment with
175198
`vpc_direct_egress = "PRIVATE_RANGES_ONLY"`. Only requests to private
@@ -199,13 +222,28 @@ the same trust boundary. Leave free capacity for `/28` allocation blocks,
199222
roughly two addresses per running service instance, and overlapping revision
200223
retention; an already occupied `/26` is not sufficient.
201224

225+
In a Shared VPC, also audit hierarchical firewall policies and every existing
226+
host-project ingress rule that can reach the VM. The narrow rule owned by one
227+
cloud-compose application state cannot cancel a broader rule owned elsewhere.
228+
Record the subnet's other consumers and re-audit them before widening either
229+
the source range or the application port.
230+
202231
A release that provides power management must declare bounded power-on, TCP
203232
connection, and Cloud Run request budgets. Check the selected release for the
204233
tested values. The proxy adds no application-level request or response-status
205234
retry after a connection succeeds; Go may still retry requests it classifies
206235
as replayable when a pooled connection is found stale. Operators and clients
207236
must still tolerate occasional connection resets during network maintenance.
208237

238+
Cloud-compose `1.5.0` retains the tested direct public PPB path with a 240-second
239+
power-on budget and a 300-second backend-dial budget inside the 600-second
240+
Cloud Run request ceiling. Its hosted GCP gate sends one request, verifies the
241+
VM reaches `RUNNING`, follows a changed ephemeral external address after a
242+
stop/start, then performs host-level and application checks. Those direct-path
243+
budgets do not fit behind non-Enterprise Cloudflare's 120-second read limit;
244+
the managed router uses a separate pre-dispatch retry contract documented in
245+
[Security and Operations](/platform/security-operations).
246+
209247
In releases that expose the hardened power-management contract, enabling it
210248
requires explicit original-client CIDRs in
211249
`gcp.network.power_button_allowed_ips`; cloud-compose does not silently trust
@@ -229,12 +267,16 @@ depth zero rather than trust the incoming `X-Forwarded-For` chain. See [Security
229267
and Operations](/platform/security-operations) for that target managed request
230268
path.
231269

232-
Google recommends an egress-aware startup probe for ordinary Direct VPC
233-
services. The power-button proxy cannot use application reachability as its
234-
startup gate because the application VM is intentionally allowed to be off;
270+
Google warns that a new Direct VPC instance can take a minute or more to
271+
establish a connection and recommends an egress-aware startup probe with
272+
retries for ordinary services. The power-button proxy cannot use application
273+
reachability as its startup gate because the application VM is intentionally
274+
allowed to be off;
235275
the proxy has to accept the first request before it can start that VM. Its
236-
bounded backend dial loop is the retry boundary for this design. A generic
237-
process-only probe would not prove VPC connectivity.
276+
bounded backend dial loop is the retry boundary for this design. The selected
277+
release therefore uses a process-only startup probe to avoid deadlocking the
278+
wake path. That probe proves only that PPB can accept requests; the hosted
279+
first-request smoke test is the evidence for Direct VPC connectivity.
238280

239281
Cloud Run can retain addresses for up to 20 minutes after a service revision
240282
scales down. After a service is deleted or moved away, wait one to two hours
@@ -309,6 +351,15 @@ When opting in:
309351

310352
When a selected release advertises verified managed artifacts, it must verify downloaded Docker plugins, sitectl packages, rootfs archives, and optional rollout artifacts before atomic installation. Preserve those checks when replacing an installer or adding another managed artifact.
311353

354+
Automated Docker cleanup is disabled by default and must remain independent of
355+
key rotation or application rollout. If an operator enables the dedicated
356+
cleanup timer, preserve rollback-tagged images: prune old stopped containers,
357+
unused networks, dangling images, and build cache explicitly, with an age
358+
filter and a single host lock. Do not restore `docker system prune --all`; it
359+
can remove the named image retained for a local rollback. The safer policy may
360+
retain unused named images, so monitor disk use and remove an obsolete named
361+
release only after its rollback window closes.
362+
312363
## Adopting the modular runtime contract
313364

314365
Use the `required_version` declared by the exact module source. A release that adopts the modular runtime contract changes power management and privileged internal services from implicit behavior to explicit opt-in. To preserve the earlier GCP behavior, configure the controls exposed by that release before advancing the module ref:

0 commit comments

Comments
 (0)