@crossdelta/infrastructure 0.2.11 → 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +303 -41
- package/dist/helpers/droplet-builder.d.ts +38 -0
- package/dist/helpers/index.d.ts +1 -0
- package/dist/index.cjs +311692 -1
- package/dist/index.js +311680 -1
- package/dist/types/index.d.ts +70 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,15 +3,64 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@crossdelta/infrastructure)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
Infrastructure-as-Code helpers for deploying microservices to DigitalOcean
|
|
6
|
+
Infrastructure-as-Code helpers for deploying microservices to DigitalOcean with Pulumi.
|
|
7
|
+
|
|
8
|
+
> **Note**: This is an MVP architecture optimized for simplicity and cost-efficiency.
|
|
9
|
+
> For production with SLAs, consider managed services or HA clustering.
|
|
10
|
+
|
|
11
|
+
## Architecture Overview
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
+-----------------------------------------------------------------------------+
|
|
15
|
+
| DigitalOcean |
|
|
16
|
+
| +-----------------------------------------------------------------------+ |
|
|
17
|
+
| | App Platform | |
|
|
18
|
+
| | +-------------+ +-------------+ +-------------+ | |
|
|
19
|
+
| | | frontend | | api | | worker | | |
|
|
20
|
+
| | | (public) | | (public) | | (internal) | | |
|
|
21
|
+
| | | :3000 | | :4000 | | :4001 | | |
|
|
22
|
+
| | +-------------+ +-------------+ +-------------+ | |
|
|
23
|
+
| | | | | | |
|
|
24
|
+
| +---------|----------------|----------------|---------------------------+ |
|
|
25
|
+
| | | | |
|
|
26
|
+
| +----------------+----------------+ |
|
|
27
|
+
| | VPC (private network) |
|
|
28
|
+
| v |
|
|
29
|
+
| +-----------------------------------------------------------------------+ |
|
|
30
|
+
| | Droplet | |
|
|
31
|
+
| | +---------------------------------------------+ | |
|
|
32
|
+
| | | NATS + JetStream | | |
|
|
33
|
+
| | | :4222 (NATS) | | |
|
|
34
|
+
| | | :8222 (HTTP monitoring) | | |
|
|
35
|
+
| | +---------------------------------------------+ | |
|
|
36
|
+
| | | | |
|
|
37
|
+
| | +---------+ | |
|
|
38
|
+
| | | Volume | (persistent storage) | |
|
|
39
|
+
| | | /data | | |
|
|
40
|
+
| | +---------+ | |
|
|
41
|
+
| +-----------------------------------------------------------------------+ |
|
|
42
|
+
+-----------------------------------------------------------------------------+
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Platform Strategy
|
|
46
|
+
|
|
47
|
+
| Platform | Use Case | Pros | Cons |
|
|
48
|
+
|----------|----------|------|------|
|
|
49
|
+
| **App Platform** | Stateless services (APIs, frontends) | Auto-scaling, zero-ops, built-in networking | No persistent volumes |
|
|
50
|
+
| **Droplet** | Stateful services (NATS, databases) | Persistent volumes, full control | Manual management |
|
|
51
|
+
|
|
52
|
+
**Why this split?** DigitalOcean App Platform doesn't support persistent volumes.
|
|
53
|
+
Services like NATS with JetStream need persistent storage for message durability.
|
|
7
54
|
|
|
8
55
|
## Features
|
|
9
56
|
|
|
57
|
+
- **Dual Platform Support** - Deploy to App Platform or Droplets from the same config
|
|
10
58
|
- **Service Discovery** - Auto-discover service configs from `infra/services/*.ts`
|
|
11
59
|
- **Type-safe Config** - Full TypeScript support with `ServiceConfig` type
|
|
12
60
|
- **Smart Routing** - Automatic ingress rules for public services
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
61
|
+
- **VPC Networking** - Secure private communication between platforms
|
|
62
|
+
- **Persistent Volumes** - Automatic volume creation and mounting for Droplets
|
|
63
|
+
- **Config File Mounting** - Share config files between local dev and production
|
|
15
64
|
- **URL Generation** - Auto-generate service URLs and port env vars
|
|
16
65
|
|
|
17
66
|
## Installation
|
|
@@ -21,17 +70,44 @@ npm install @crossdelta/infrastructure
|
|
|
21
70
|
```
|
|
22
71
|
|
|
23
72
|
## Quick Start
|
|
24
|
-
|
|
73
|
+
|
|
74
|
+
### 1. Create service configs
|
|
25
75
|
|
|
26
76
|
```typescript
|
|
27
|
-
// infra/services/api
|
|
77
|
+
// infra/services/api.ts - App Platform (stateless)
|
|
28
78
|
import type { ServiceConfig } from '@crossdelta/infrastructure'
|
|
29
79
|
|
|
30
80
|
const config: ServiceConfig = {
|
|
31
|
-
name: 'api
|
|
32
|
-
instanceSizeSlug: 'apps-s-1vcpu-0.5gb',
|
|
81
|
+
name: 'api',
|
|
33
82
|
httpPort: 4000,
|
|
34
83
|
ingressPrefix: '/api',
|
|
84
|
+
instanceSizeSlug: 'apps-s-1vcpu-0.5gb',
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default config
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// infra/services/nats.ts - Droplet (stateful with persistence)
|
|
92
|
+
import { dockerHubImage, type ServiceConfig } from '@crossdelta/infrastructure'
|
|
93
|
+
import { readFileSync } from 'node:fs'
|
|
94
|
+
|
|
95
|
+
const config: ServiceConfig = {
|
|
96
|
+
name: 'nats',
|
|
97
|
+
platform: 'droplet',
|
|
98
|
+
internalPorts: [4222, 8222],
|
|
99
|
+
internalUrl: 'nats://nats:4222',
|
|
100
|
+
image: dockerHubImage('nats', '2.10-alpine'),
|
|
101
|
+
runCommand: '-c /etc/nats/nats.conf',
|
|
102
|
+
droplet: {
|
|
103
|
+
size: 's-1vcpu-1gb',
|
|
104
|
+
volumes: [{ name: 'nats-data', mountPath: '/data', sizeGb: 10 }],
|
|
105
|
+
vpcPorts: [4222, 8222],
|
|
106
|
+
configFile: {
|
|
107
|
+
containerPath: '/etc/nats/nats.conf',
|
|
108
|
+
content: readFileSync('path/to/nats.conf', 'utf-8'),
|
|
109
|
+
},
|
|
110
|
+
},
|
|
35
111
|
}
|
|
36
112
|
|
|
37
113
|
export default config
|
|
@@ -46,60 +122,215 @@ import {
|
|
|
46
122
|
buildIngressRules,
|
|
47
123
|
buildServiceUrlEnvs,
|
|
48
124
|
buildServicePortEnvs,
|
|
125
|
+
buildDropletServices,
|
|
126
|
+
filterByPlatform,
|
|
49
127
|
discoverServices,
|
|
50
128
|
} from '@crossdelta/infrastructure'
|
|
51
|
-
import { App } from '@pulumi/digitalocean'
|
|
129
|
+
import { App, Vpc } from '@pulumi/digitalocean'
|
|
52
130
|
|
|
131
|
+
const region = 'fra1'
|
|
53
132
|
const serviceConfigs = discoverServices('services')
|
|
54
133
|
|
|
134
|
+
// Split services by platform
|
|
135
|
+
const { appPlatformServices, dropletServices } = filterByPlatform(serviceConfigs)
|
|
136
|
+
|
|
137
|
+
// Create VPC for private networking
|
|
138
|
+
const vpc = new Vpc('platform-vpc', {
|
|
139
|
+
name: 'platform-vpc',
|
|
140
|
+
region,
|
|
141
|
+
ipRange: '10.10.10.0/24',
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
// Deploy Droplet services (stateful)
|
|
145
|
+
const dropletResources = buildDropletServices({
|
|
146
|
+
serviceConfigs: dropletServices,
|
|
147
|
+
region,
|
|
148
|
+
vpcUuid: vpc.id,
|
|
149
|
+
projectName: 'my-platform',
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// Get env vars for Droplet service URLs
|
|
153
|
+
const dropletServiceEnvs = dropletResources.flatMap(r => [
|
|
154
|
+
{ key: `${r.serviceName.toUpperCase()}_URL`, value: r.internalUrl },
|
|
155
|
+
{ key: `${r.serviceName.toUpperCase()}_HOST`, value: r.privateIp },
|
|
156
|
+
])
|
|
157
|
+
|
|
158
|
+
// Deploy App Platform services (stateless)
|
|
55
159
|
const app = new App('my-app', {
|
|
56
160
|
spec: {
|
|
57
161
|
name: 'my-platform',
|
|
58
|
-
region: 'fra'
|
|
162
|
+
region: region.replace(/\d+$/, ''), // 'fra1' -> 'fra'
|
|
59
163
|
envs: [
|
|
60
|
-
...buildServiceUrlEnvs(
|
|
61
|
-
...buildServicePortEnvs(
|
|
164
|
+
...buildServiceUrlEnvs(appPlatformServices),
|
|
165
|
+
...buildServicePortEnvs(appPlatformServices),
|
|
166
|
+
...dropletServiceEnvs,
|
|
62
167
|
],
|
|
63
|
-
services: buildServices({ serviceConfigs }),
|
|
64
|
-
ingress: { rules: buildIngressRules(
|
|
168
|
+
services: buildServices({ serviceConfigs: appPlatformServices }),
|
|
169
|
+
ingress: { rules: buildIngressRules(appPlatformServices) },
|
|
65
170
|
},
|
|
66
171
|
})
|
|
67
172
|
```
|
|
68
173
|
|
|
174
|
+
## ServiceConfig API
|
|
175
|
+
|
|
176
|
+
The `ServiceConfig` type is the central configuration for all services:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
type ServiceConfig = {
|
|
180
|
+
/** Unique name of the service (required) */
|
|
181
|
+
name: string
|
|
182
|
+
|
|
183
|
+
/** Deployment platform: 'app-platform' (default) or 'droplet' */
|
|
184
|
+
platform?: 'app-platform' | 'droplet'
|
|
185
|
+
|
|
186
|
+
/** Public HTTP port with ingress routing (requires ingressPrefix) */
|
|
187
|
+
httpPort?: number
|
|
188
|
+
|
|
189
|
+
/** Internal-only ports, not publicly accessible */
|
|
190
|
+
internalPorts?: number[]
|
|
191
|
+
|
|
192
|
+
/** Ingress path prefix for public routing (e.g., '/api') */
|
|
193
|
+
ingressPrefix?: string
|
|
194
|
+
|
|
195
|
+
/** Override internal URL (e.g., 'nats://nats:4222' for non-HTTP) */
|
|
196
|
+
internalUrl?: string
|
|
197
|
+
|
|
198
|
+
/** Exclude from deployment */
|
|
199
|
+
skip?: boolean
|
|
200
|
+
|
|
201
|
+
/** Custom image configuration */
|
|
202
|
+
image?: {
|
|
203
|
+
registryType: 'DOCKER_HUB' | 'GHCR' | 'DOCR'
|
|
204
|
+
registry: string // e.g., 'library', 'myorg'
|
|
205
|
+
repository: string // e.g., 'nats', 'platform/api'
|
|
206
|
+
tag: string // e.g., '2.10-alpine', 'latest'
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Command to run in the container */
|
|
210
|
+
runCommand?: string
|
|
211
|
+
|
|
212
|
+
/** App Platform: instance size (e.g., 'apps-s-1vcpu-0.5gb') */
|
|
213
|
+
instanceSizeSlug?: string
|
|
214
|
+
|
|
215
|
+
/** Droplet-specific configuration */
|
|
216
|
+
droplet?: {
|
|
217
|
+
/** Droplet size (e.g., 's-1vcpu-1gb') */
|
|
218
|
+
size: string
|
|
219
|
+
|
|
220
|
+
/** Persistent volumes */
|
|
221
|
+
volumes?: Array<{
|
|
222
|
+
name: string // Volume name (unique)
|
|
223
|
+
mountPath: string // Path in container (e.g., '/data')
|
|
224
|
+
sizeGb: number // Size in GB
|
|
225
|
+
}>
|
|
226
|
+
|
|
227
|
+
/** Ports to open in VPC firewall */
|
|
228
|
+
vpcPorts?: number[]
|
|
229
|
+
|
|
230
|
+
/** Additional Docker run arguments */
|
|
231
|
+
dockerArgs?: string
|
|
232
|
+
|
|
233
|
+
/** Config file to mount into container */
|
|
234
|
+
configFile?: {
|
|
235
|
+
containerPath: string // Path in container
|
|
236
|
+
content: string // File content
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
69
242
|
## Service Configuration Examples
|
|
70
243
|
|
|
71
|
-
### Public Service
|
|
244
|
+
### Public Service (App Platform)
|
|
72
245
|
|
|
73
246
|
```typescript
|
|
74
247
|
const config: ServiceConfig = {
|
|
75
|
-
name: '
|
|
248
|
+
name: 'frontend',
|
|
76
249
|
httpPort: 3000,
|
|
77
250
|
ingressPrefix: '/',
|
|
78
251
|
instanceSizeSlug: 'apps-s-1vcpu-1gb',
|
|
79
252
|
}
|
|
80
253
|
```
|
|
81
254
|
|
|
82
|
-
### Internal Service
|
|
255
|
+
### Internal Service (App Platform)
|
|
83
256
|
|
|
84
257
|
```typescript
|
|
85
258
|
const config: ServiceConfig = {
|
|
86
|
-
name: '
|
|
259
|
+
name: 'worker',
|
|
87
260
|
internalPorts: [4001],
|
|
88
261
|
instanceSizeSlug: 'apps-s-1vcpu-0.5gb',
|
|
89
262
|
}
|
|
90
263
|
```
|
|
91
264
|
|
|
92
|
-
###
|
|
265
|
+
### Stateful Service with Persistence (Droplet)
|
|
93
266
|
|
|
94
267
|
```typescript
|
|
268
|
+
import { dockerHubImage, type ServiceConfig } from '@crossdelta/infrastructure'
|
|
269
|
+
|
|
95
270
|
const config: ServiceConfig = {
|
|
96
271
|
name: 'nats',
|
|
272
|
+
platform: 'droplet',
|
|
97
273
|
internalPorts: [4222, 8222],
|
|
98
274
|
internalUrl: 'nats://nats:4222',
|
|
99
|
-
|
|
275
|
+
image: dockerHubImage('nats', '2.10-alpine'),
|
|
276
|
+
runCommand: '-c /etc/nats/nats.conf',
|
|
277
|
+
droplet: {
|
|
278
|
+
size: 's-1vcpu-1gb',
|
|
279
|
+
volumes: [{ name: 'nats-data', mountPath: '/data', sizeGb: 10 }],
|
|
280
|
+
vpcPorts: [4222, 8222],
|
|
281
|
+
configFile: {
|
|
282
|
+
containerPath: '/etc/nats/nats.conf',
|
|
283
|
+
content: readFileSync('services/nats/nats.conf', 'utf-8'),
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Config File Sharing (Local + Production)
|
|
290
|
+
|
|
291
|
+
One of the key features is sharing configuration between local development and production:
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
services/nats/
|
|
295
|
+
├── nats.conf # Shared config (used by both)
|
|
296
|
+
└── scripts/
|
|
297
|
+
└── start-dev.sh # Local dev script
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**nats.conf** (shared):
|
|
301
|
+
```conf
|
|
302
|
+
server_name: my-nats-server
|
|
303
|
+
listen: 0.0.0.0:4222
|
|
304
|
+
http: 0.0.0.0:8222
|
|
305
|
+
|
|
306
|
+
jetstream {
|
|
307
|
+
store_dir: /data
|
|
308
|
+
max_mem: 1G
|
|
309
|
+
max_file: 10G
|
|
100
310
|
}
|
|
101
311
|
```
|
|
102
312
|
|
|
313
|
+
**start-dev.sh** (local):
|
|
314
|
+
```bash
|
|
315
|
+
docker run -d --name nats \
|
|
316
|
+
-v "$(pwd)/nats.conf:/etc/nats/nats.conf:ro" \
|
|
317
|
+
-v "$(pwd)/.nats-data:/data" \
|
|
318
|
+
-p 4222:4222 -p 8222:8222 \
|
|
319
|
+
nats:2.10-alpine -c /etc/nats/nats.conf
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**infra/services/nats.ts** (production):
|
|
323
|
+
```typescript
|
|
324
|
+
droplet: {
|
|
325
|
+
configFile: {
|
|
326
|
+
containerPath: '/etc/nats/nats.conf',
|
|
327
|
+
content: readFileSync('services/nats/nats.conf', 'utf-8'),
|
|
328
|
+
},
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
→ Same config file, same behavior, different environments.
|
|
333
|
+
|
|
103
334
|
## CLI: generate-env
|
|
104
335
|
|
|
105
336
|
Generate `.env.local` and service-specific `env.ts` files for local development.
|
|
@@ -116,8 +347,6 @@ Add a script to your workspace `package.json`:
|
|
|
116
347
|
}
|
|
117
348
|
```
|
|
118
349
|
|
|
119
|
-
Or use `pf dev` which runs `generate-env` automatically before starting services.
|
|
120
|
-
|
|
121
350
|
### What it does
|
|
122
351
|
|
|
123
352
|
1. Loads secrets from Pulumi config (dev stack)
|
|
@@ -132,21 +361,13 @@ Or use `pf dev` which runs `generate-env` automatically before starting services
|
|
|
132
361
|
`.env.local`:
|
|
133
362
|
```bash
|
|
134
363
|
# Service URLs (localhost for local development)
|
|
135
|
-
|
|
136
|
-
|
|
364
|
+
API_URL=http://localhost:4000
|
|
365
|
+
FRONTEND_URL=http://localhost:3000
|
|
366
|
+
NATS_URL=nats://localhost:4222
|
|
137
367
|
|
|
138
368
|
# Service Ports
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
`services/orders/src/env.ts`:
|
|
144
|
-
```typescript
|
|
145
|
-
import { getServicePort, getServiceUrl } from '@crossdelta/infrastructure/env'
|
|
146
|
-
|
|
147
|
-
export const SERVICE_NAME = 'orders' as const
|
|
148
|
-
export const getServicePort = (defaultPort = 8080) => _getServicePort(SERVICE_NAME, defaultPort)
|
|
149
|
-
export const getServiceUrl = () => _getServiceUrl(SERVICE_NAME)
|
|
369
|
+
API_PORT=4000
|
|
370
|
+
FRONTEND_PORT=3000
|
|
150
371
|
```
|
|
151
372
|
|
|
152
373
|
## Runtime Helpers
|
|
@@ -156,27 +377,51 @@ Use `@crossdelta/infrastructure/env` in your services to read environment config
|
|
|
156
377
|
```typescript
|
|
157
378
|
import { getServicePort, getServiceUrl } from '@crossdelta/infrastructure/env'
|
|
158
379
|
|
|
159
|
-
// Get port from
|
|
160
|
-
const port = getServicePort('
|
|
380
|
+
// Get port from WORKER_PORT env var (fallback: 8080)
|
|
381
|
+
const port = getServicePort('worker', 8080)
|
|
161
382
|
|
|
162
|
-
// Get URL from
|
|
163
|
-
const url = getServiceUrl('
|
|
383
|
+
// Get URL from WORKER_URL env var
|
|
384
|
+
const url = getServiceUrl('worker')
|
|
164
385
|
```
|
|
165
386
|
|
|
166
387
|
These helpers work both locally (via `.env.local`) and in production (via DO App Platform env injection).
|
|
167
388
|
|
|
168
|
-
## API
|
|
389
|
+
## API Reference
|
|
169
390
|
|
|
170
391
|
### Main Export (`@crossdelta/infrastructure`)
|
|
171
392
|
|
|
172
393
|
| Function | Description |
|
|
173
394
|
|----------|-------------|
|
|
174
|
-
| `discoverServices(dir)` | Auto-discover service configs
|
|
395
|
+
| `discoverServices(dir)` | Auto-discover service configs from directory |
|
|
175
396
|
| `buildServices(options)` | Build App Platform service specs |
|
|
176
|
-
| `buildIngressRules(configs)` | Generate ingress rules |
|
|
397
|
+
| `buildIngressRules(configs)` | Generate ingress rules for public services |
|
|
177
398
|
| `buildServiceUrlEnvs(configs)` | Create SERVICE_NAME_URL env vars |
|
|
178
399
|
| `buildServicePortEnvs(configs)` | Create SERVICE_NAME_PORT env vars |
|
|
179
400
|
| `buildLocalUrls(configs)` | Generate localhost URLs for local dev |
|
|
401
|
+
| `filterByPlatform(configs)` | Split configs into App Platform and Droplet services |
|
|
402
|
+
| `buildDropletServices(options)` | Create Droplet resources with volumes and firewall |
|
|
403
|
+
| `dockerHubImage(repo, tag, registry?)` | Create Docker Hub image config (defaults to `library`) |
|
|
404
|
+
|
|
405
|
+
### Droplet Builder Options
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
buildDropletServices({
|
|
409
|
+
serviceConfigs: ServiceConfig[] // Services with platform: 'droplet'
|
|
410
|
+
region: string // DO region (e.g., 'fra1')
|
|
411
|
+
vpcUuid: pulumi.Output<string> // VPC ID for private networking
|
|
412
|
+
projectName: string // Prefix for resource names
|
|
413
|
+
})
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Returns an array of:
|
|
417
|
+
```typescript
|
|
418
|
+
{
|
|
419
|
+
serviceName: string // Service name
|
|
420
|
+
droplet: digitalocean.Droplet // Created Droplet resource
|
|
421
|
+
privateIp: pulumi.Output<string> // Private IP in VPC
|
|
422
|
+
internalUrl: string // URL for service discovery
|
|
423
|
+
}
|
|
424
|
+
```
|
|
180
425
|
|
|
181
426
|
### Env Export (`@crossdelta/infrastructure/env`)
|
|
182
427
|
|
|
@@ -185,6 +430,23 @@ These helpers work both locally (via `.env.local`) and in production (via DO App
|
|
|
185
430
|
| `getServicePort(name, default)` | Read SERVICE_NAME_PORT from env |
|
|
186
431
|
| `getServiceUrl(name)` | Read SERVICE_NAME_URL from env |
|
|
187
432
|
|
|
433
|
+
## MVP Limitations & Future Improvements
|
|
434
|
+
|
|
435
|
+
This architecture is designed for MVP/Beta deployments:
|
|
436
|
+
|
|
437
|
+
| Current (MVP) | Future (Production) |
|
|
438
|
+
|---------------|---------------------|
|
|
439
|
+
| Single NATS Droplet | NATS Cluster (3 nodes) or Synadia Cloud |
|
|
440
|
+
| No auto-healing | Kubernetes or managed service |
|
|
441
|
+
| Manual scaling | Auto-scaling policies |
|
|
442
|
+
| Basic monitoring | Prometheus/Grafana stack |
|
|
443
|
+
|
|
444
|
+
**Cost estimate (MVP)**:
|
|
445
|
+
- App Platform: ~$12-24/month (depending on instance sizes)
|
|
446
|
+
- NATS Droplet: ~$6/month (s-1vcpu-1gb)
|
|
447
|
+
- Volume: ~$1/month (10GB)
|
|
448
|
+
- **Total**: ~$19-31/month
|
|
449
|
+
|
|
188
450
|
## License
|
|
189
451
|
|
|
190
452
|
MIT
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as digitalocean from '@pulumi/digitalocean';
|
|
2
|
+
import * as pulumi from '@pulumi/pulumi';
|
|
3
|
+
import type { ServiceConfig } from '../types';
|
|
4
|
+
export interface DropletServiceResources {
|
|
5
|
+
droplet: digitalocean.Droplet;
|
|
6
|
+
volume?: digitalocean.Volume;
|
|
7
|
+
volumeAttachment?: digitalocean.VolumeAttachment;
|
|
8
|
+
firewall?: digitalocean.Firewall;
|
|
9
|
+
/** Private IP for VPC communication */
|
|
10
|
+
privateIp: pulumi.Output<string>;
|
|
11
|
+
}
|
|
12
|
+
export interface BuildDropletServicesOptions {
|
|
13
|
+
/** Service configs with platform: 'droplet' */
|
|
14
|
+
serviceConfigs: ServiceConfig[];
|
|
15
|
+
/** DigitalOcean region (e.g., 'fra1') */
|
|
16
|
+
region: string;
|
|
17
|
+
/** VPC UUID for private networking */
|
|
18
|
+
vpcUuid: pulumi.Input<string>;
|
|
19
|
+
/** SSH key IDs for Droplet access (optional) */
|
|
20
|
+
sshKeyIds?: pulumi.Input<pulumi.Input<string>[]>;
|
|
21
|
+
/** Registry credentials for private images (optional) */
|
|
22
|
+
registryCredentials?: pulumi.Output<string>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Builds Droplet resources for services with platform: 'droplet'.
|
|
26
|
+
*
|
|
27
|
+
* Creates:
|
|
28
|
+
* - Droplet with Docker and cloud-init script
|
|
29
|
+
* - Volume(s) for persistent storage (if configured)
|
|
30
|
+
* - Firewall rules for VPC access
|
|
31
|
+
*
|
|
32
|
+
* @returns Map of service name to created resources
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildDropletServices(options: BuildDropletServicesOptions): Map<string, DropletServiceResources>;
|
|
35
|
+
/**
|
|
36
|
+
* Filters service configs by platform type.
|
|
37
|
+
*/
|
|
38
|
+
export declare function filterByPlatform(configs: ServiceConfig[], platform: 'app-platform' | 'droplet'): ServiceConfig[];
|
package/dist/helpers/index.d.ts
CHANGED