Pulumi

The Pulumi component shape for a Flux deployment — TypeScript and Python sketches, plus the rotation and destruction guardrails that apply.

This page describes the component surface and shows TypeScript and Python sketches. The intent is to give you a target to author against. The same set of guardrails from the Terraform page applies — rotation must not be silent, encryption-key resources must be protected from destruction, and server replica counts above one are only valid with PostgreSQL plus sticky routing on the load balancer.

Component set

Four components mirror the Terraform module set.

FluxPostgres

The database tier.

Inputs (TypeScript):

interface FluxPostgresArgs {
  name: string;
  version?: string;                  // "16"
  instanceSize: string;              // provider-shaped
  vpcId: pulumi.Input<string>;
  subnetIds: pulumi.Input<string[]>;
  multiAz?: boolean;                 // default true
  backupRetentionDays?: number;      // default 7
  tags?: Record<string, string>;
}

Outputs:

FluxSecrets

Bootstrap configuration.

Inputs:

Outputs:

The component generates the bootstrap token and encryption key on first run via a RandomPassword or random.RandomBytes resource, stores them in the cloud’s secret manager, and exports the references. The crucial bit: the resource has pulumi.ResourceOptions({ protect: true, ignoreChanges: ["..."] }) so a routine pulumi up doesn’t rotate them silently.

FluxServer

The server tier.

Inputs:

Outputs:

The component maps replicaCount onto the platform’s fixed-count knob (desiredCount on ECS, minInstances/maxInstances on Cloud Run, minReplicas/maxReplicas on Container Apps) with no auto-scaling policy on the server tier — replica count is an availability decision, and each replica adds its connection-pool ceiling (40 by default) to the PostgreSQL budget. At replicaCount > 1 the component must also enable connection affinity on the load-balancer resource it wires up (target-group stickiness, sessionAffinity, sticky sessions) — a worker’s SSE stream lives on the replica it connected to — and its constructor should throw if the database URL is SQLite, which is single-node only. See High availability.

FluxWorkerPool

The worker tier — one per pool.

Inputs:

TypeScript composition sketch

import * as pulumi from "@pulumi/pulumi";
import { FluxPostgres, FluxSecrets, FluxServer, FluxWorkerPool } from "@pulumi/flux";

const network = /* your VPC component */;

const db = new FluxPostgres("flux-db", {
  name: "flux",
  instanceSize: "db.t4g.medium",
  vpcId: network.vpcId,
  subnetIds: network.privateSubnetIds,
  multiAz: true,
});

const secrets = new FluxSecrets("flux-secrets", {
  namePrefix: "flux",
  databaseUrl: db.connectionUrl,
});

const server = new FluxServer("flux-server", {
  image: "myregistry/flux:0.56.0",
  bootstrapTokenRef: secrets.bootstrapTokenRef,
  encryptionKeyRef: secrets.encryptionKeyRef,
  databaseUrlRef: secrets.databaseUrlRef,
  serviceSubnetIds: network.privateSubnetIds,
  loadBalancerTargetGroupArn: lb.targetGroupArn,
  authProvider: "apiKeys",
  replicaCount: 2,   // >1 requires PostgreSQL + sticky routing on the LB
});

new FluxWorkerPool("worker-default", {
  poolName: "default",
  image: "myregistry/flux:0.56.0",
  bootstrapTokenRef: secrets.bootstrapTokenRef,
  serverUrl: server.internalUrl,
  minReplicas: 2,
  maxReplicas: 20,
  scaleMetric: "cpu",
});

new FluxWorkerPool("worker-gpu", {
  poolName: "gpu",
  image: "myregistry/flux:0.56.0",
  bootstrapTokenRef: secrets.bootstrapTokenRef,
  serverUrl: server.internalUrl,
  minReplicas: 0,
  maxReplicas: 5,
  scaleMetric: "cpu",
  extraLabels: { gpu: "true" },
});

Python composition sketch

import pulumi
from pulumi_flux import FluxPostgres, FluxSecrets, FluxServer, FluxWorkerPool

db = FluxPostgres("flux-db",
    name="flux",
    instance_size="db.t4g.medium",
    vpc_id=network.vpc_id,
    subnet_ids=network.private_subnet_ids,
    multi_az=True)

secrets = FluxSecrets("flux-secrets",
    name_prefix="flux",
    database_url=db.connection_url)

server = FluxServer("flux-server",
    image="myregistry/flux:0.56.0",
    bootstrap_token_ref=secrets.bootstrap_token_ref,
    encryption_key_ref=secrets.encryption_key_ref,
    database_url_ref=secrets.database_url_ref,
    service_subnet_ids=network.private_subnet_ids,
    load_balancer_target_group_arn=lb.target_group_arn,
    auth_provider="api_keys")

FluxWorkerPool("worker-default",
    pool_name="default",
    image="myregistry/flux:0.56.0",
    bootstrap_token_ref=secrets.bootstrap_token_ref,
    server_url=server.internal_url,
    min_replicas=2,
    max_replicas=20,
    scale_metric="cpu")

The sketches are illustrative — @pulumi/flux / pulumi_flux are not real packages today.

Guardrails

The same rotation and destruction concerns from Terraform apply, expressed in Pulumi’s vocabulary.

What can go wrong

pulumi up triggers a replacement of the bootstrap secret

Symptom. A routine pulumi up plan shows ~ flux-secrets:bootstrap-token as needing replacement; applying it logs every worker out.

Cause. ignoreChanges is missing on the secret-version resource, or a keepers-style trigger derives from a value that drifted.

Fix. Add pulumi.ResourceOptions({ ignoreChanges: ["secretString"] }) to the secret-version resource. Rotation moves to a separate flow with pulumi up --target on a dedicated rotation stack.

Destroying the dev stack destroyed the encryption key

Symptom. A teammate ran pulumi destroy on the dev stack and the encryption key went with it. Every encrypted column in dev is unreadable.

Cause. The encryption-key resource lived in the same stack as the rest of the dev environment, with no protect: true.

Fix. Move the key resource to a separate stack and add protect: true. For the affected dev environment, the recovery is “restore from backup” if you have one for dev, or “rebuild from scratch and accept the secret loss” if you don’t.

Cross-stack reference broke after a refactor

Symptom. StackReference lookups return empty values after one stack was renamed or refactored.

Cause. The reference name didn’t match after the change.

Fix. Update the StackReference name; redeploy. For the long-term, name stack outputs deliberately and don’t change them without a deprecation cycle.

Modules coming soon

An official @pulumi/flux (and pulumi_flux) package is planned. Until it ships, use the shape on this page as your authoring target.

Next