@crossdelta/infrastructure
v0.12.3
Published
[](https://www.npmjs.com/package/@crossdelta/infrastructure) [](https://opensource.org/licenses/MIT)
Readme
@crossdelta/infrastructure
Opinionated Pulumi library for deploying microservices to DigitalOcean Kubernetes (DOKS). You describe each service as a typed config object, the package generates Deployments, Services, Ingress, Secrets, health probes, and rolling update strategies.
Built for teams running TypeScript microservices on DOKS with NATS JetStream, GHCR, and Pulumi. If that's your stack, this saves real boilerplate. If not, you're probably better off with plain Pulumi or Helm.
When to use this
- You deploy multiple TypeScript services to a DigitalOcean Kubernetes cluster
- You use Pulumi (not Terraform, not Helm-only) for infrastructure
- You want one config object per service instead of ~150 lines of K8s resource declarations
- You need NATS JetStream, NGINX Ingress, cert-manager, or Caddy as a reverse proxy
When NOT to use this
- AWS/GCP/Azure: Only DOKS is implemented. EKS/AKS/GKE are not supported.
- Terraform or plain Helm: This is Pulumi-only.
- Non-opinionated setups: The package makes choices for you (Helm chart versions, deployment strategies, DO-specific annotations). If you need full control, use Pulumi directly.
- Single-service projects: The overhead isn't worth it for one service.
Install
npm install @crossdelta/infrastructure @pulumi/pulumi @pulumi/kubernetes
# For DOKS cluster creation:
npm install @pulumi/digitalocean
# For NATS stream deployment (optional):
npm install @crossdelta/cloudeventsWhat's included
| Module | What it does |
|--------|-------------|
| Cluster | createDOKSCluster(), createVPC(), createK8sProviderFromKubeconfig() |
| Workloads | deployK8sService(), deployK8sServices(), createNamespace(), createImagePullSecret() |
| NGINX Ingress | deployNginxIngress() via Helm (chart v4.11.3) |
| cert-manager | deployCertManager() via Helm (chart v1.16.2) |
| NATS | deployNats() via Helm (chart v1.2.6), buildNatsUrl() |
| Caddy | deployCaddy(), generateCaddyfile() for reverse proxy with on-demand TLS |
| Streams | collectStreamDefinitions(), deployStreams(), materializeStreams() (NATS JetStream) |
| Service Discovery | discoverServiceConfigs() auto-discovers configs from infra/services/*.ts |
| Local Dev | generateComposeYaml(), k3d cluster scripts |
| CLI | generate-env generates .env.local from Pulumi config + service discovery |
Quick Start
One index.ts that provisions a cluster, runtime components, and all services:
import {
createDOKSCluster,
createVPC,
createNamespace,
deployRuntime,
deployK8sServices,
discoverServiceConfigs,
} from '@crossdelta/infrastructure'
const vpc = createVPC({ name: 'my-vpc', region: 'fra1' })
const { provider } = createDOKSCluster({
name: 'my-cluster',
vpcUuid: vpc.id,
nodePool: { name: 'default', size: 's-4vcpu-8gb', nodeCount: 1 },
})
createNamespace(provider, 'my-namespace')
const runtime = deployRuntime(provider, 'my-namespace', {
nats: { enabled: true, config: { replicas: 1, jetstream: { enabled: true } } },
ingress: { enabled: true },
certManager: { enabled: true, config: { email: '[email protected]' } },
})
const configs = discoverServiceConfigs('services')
deployK8sServices(provider, 'my-namespace', configs)Service Config
Each file in infra/services/ exports one config object. The package derives all K8s resources from it:
import { ports, type K8sServiceConfig } from '@crossdelta/infrastructure'
const config: K8sServiceConfig = {
name: 'my-service',
ports: ports().http(4000).public().build(),
healthCheck: { httpPath: '/health' },
ingress: { path: '/api', host: 'example.com' },
env: { DATABASE_URL: dbUrl },
secrets: { API_KEY: apiKey },
resources: {
requests: { cpu: '50m', memory: '64Mi' },
limits: { cpu: '200m', memory: '256Mi' },
},
}
export default configSee K8sServiceConfig for all fields: replicas, volumes, strategy, containerEnv, labels, annotations, serviceType, etc.
Shared Cluster (Multi-Stack)
Pin DigitalOcean resource names with clusterName/vpcName to share a cluster across Pulumi stacks:
// Stage stack owns the cluster
const { provider, kubeconfig } = createDOKSCluster({
name: 'my-cluster',
clusterName: 'my-cluster',
vpcUuid: vpc.id,
nodePool: { name: 'default', size: 's-4vcpu-8gb', nodeCount: 1 },
})
export const clusterKubeconfig = kubeconfig
// Production stack references stage
const stageStack = new StackReference('org/project/stage')
const provider = createK8sProviderFromKubeconfig(
'production',
stageStack.getOutput('clusterKubeconfig'),
)generate-env CLI
Generates .env.local from Pulumi secrets and discovered service configs:
npx generate-env # defaults to stage stack
npx generate-env --stack=production
npx generate-env --no-pulumi # skip Pulumi, only service discoveryLimitations
- DOKS only. No other cloud provider is implemented.
- Helm chart versions are pinned. NGINX v4.11.3, cert-manager v1.16.2, NATS v1.2.6. No override option yet.
- Service discovery assumes a convention:
infra/services/*.tsfiles withexport default config. @crossdelta/cloudeventsis a hard dependency even if you don't use NATS streams https://github.com/orderboss/platform/issues/TBD.- Caddy deployment uses Recreate strategy (RWO PVC incompatible with rolling updates).
License
MIT © crossdelta
