Pulumi brings the power of real programming languages to infrastructure as code. Unlike YAML-based tools, you get loops, conditionals, type checking, and IDE autocomplete. This guide shows you how to manage your entire DanubeData infrastructure with TypeScript.
Why Pulumi with TypeScript?
TypeScript is the most popular choice for Pulumi because it offers:
- Full type safety with IntelliSense autocomplete
- Familiar syntax for JavaScript/TypeScript developers
- Rich ecosystem of npm packages
- Compile-time error detection
- Easy integration with existing Node.js toolchains
Getting Started
Install Pulumi CLI
# macOS
brew install pulumi
# Linux
curl -fsSL https://get.pulumi.com | sh
# Windows
choco install pulumi
Create a New Project
# Create a new directory
mkdir my-infrastructure && cd my-infrastructure
# Initialize a new TypeScript project
pulumi new typescript
# Install the DanubeData provider
npm install @danubedata/pulumi
Configure Authentication
# Set your DanubeData API token
pulumi config set danubedata:apiToken your-api-token --secret
Project Structure
A typical DanubeData Pulumi project looks like this:
my-infrastructure/
├── Pulumi.yaml # Project configuration
├── Pulumi.dev.yaml # Development stack config
├── Pulumi.prod.yaml # Production stack config
├── index.ts # Main infrastructure code
├── package.json # Node.js dependencies
├── tsconfig.json # TypeScript configuration
└── resources/
├── vps.ts # VPS resources
├── database.ts # Database resources
├── cache.ts # Cache resources
└── storage.ts # Storage resources
Creating Your First VPS
// index.ts
import * as pulumi from "@pulumi/pulumi";
import * as danubedata from "@danubedata/pulumi";
// Create an SSH key for authentication
const sshKey = new danubedata.SshKey("deploy-key", {
name: "deploy-key",
publicKey: `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample... deploy@example.com`,
});
// Create a VPS instance
const webServer = new danubedata.Vps("web-server", {
name: "web-server",
image: "ubuntu-24.04",
datacenter: "fsn1",
resourceProfile: "small_shared", // 2 vCPU, 4GB RAM
authMethod: "ssh_key",
sshKeyId: sshKey.id,
enableIpv6: true,
enableBackups: true,
});
// Export the VPS public IP
export const webServerIp = webServer.publicIp;
export const webServerIpv6 = webServer.ipv6Address;
Complete Web Application Stack
Here's a complete example deploying a full application stack:
// index.ts
import * as pulumi from "@pulumi/pulumi";
import * as danubedata from "@danubedata/pulumi";
const config = new pulumi.Config();
const environment = pulumi.getStack();
const appName = config.require("appName");
// =====================
// SSH Key
// =====================
const sshKey = new danubedata.SshKey("deploy-key", {
name: `${appName}-${environment}-deploy`,
publicKey: config.requireSecret("sshPublicKey"),
});
// =====================
// PostgreSQL Database
// =====================
const database = new danubedata.Database("app-db", {
name: `${appName}-${environment}-db`,
engine: "postgresql",
engineVersion: "16",
resourceProfile: environment === "prod" ? "medium" : "small",
databaseName: appName.replace(/-/g, "_"),
datacenter: "fsn1",
enableSsl: true,
});
// =====================
// Redis Cache
// =====================
const cache = new danubedata.Cache("app-cache", {
name: `${appName}-${environment}-cache`,
cacheProvider: "redis",
resourceProfile: environment === "prod" ? "small" : "micro",
datacenter: "fsn1",
});
// =====================
// Storage Bucket
// =====================
const bucket = new danubedata.StorageBucket("app-storage", {
name: `${appName}-${environment}-assets`,
region: "fsn1",
versioningEnabled: true,
});
const storageKeys = new danubedata.StorageAccessKey("app-storage-key", {
name: `${appName}-${environment}-key`,
});
// =====================
// Application VPS
// =====================
const appServer = new danubedata.Vps("app-server", {
name: `${appName}-${environment}-app`,
image: "ubuntu-24.04",
datacenter: "fsn1",
resourceProfile: environment === "prod" ? "medium_shared" : "small_shared",
authMethod: "ssh_key",
sshKeyId: sshKey.id,
enableIpv6: true,
enableBackups: environment === "prod",
});
// =====================
// Firewall Rules
// =====================
const webFirewall = new danubedata.Firewall("web-firewall", {
name: `${appName}-${environment}-web-fw`,
rules: [
{
direction: "inbound",
protocol: "tcp",
ports: "80",
sourceIps: ["0.0.0.0/0", "::/0"],
description: "Allow HTTP",
},
{
direction: "inbound",
protocol: "tcp",
ports: "443",
sourceIps: ["0.0.0.0/0", "::/0"],
description: "Allow HTTPS",
},
{
direction: "inbound",
protocol: "tcp",
ports: "22",
sourceIps: [config.require("adminIp")],
description: "SSH from admin only",
},
],
});
// =====================
// Database Firewall
// =====================
const dbFirewall = new danubedata.Firewall("db-firewall", {
name: `${appName}-${environment}-db-fw`,
rules: [
{
direction: "inbound",
protocol: "tcp",
ports: "5432",
sourceIps: [pulumi.interpolate`${appServer.publicIp}/32`],
description: "PostgreSQL from app server",
},
],
});
// =====================
// Exports
// =====================
export const outputs = {
appServerIp: appServer.publicIp,
appServerIpv6: appServer.ipv6Address,
databaseEndpoint: database.endpoint,
databaseName: database.databaseName,
cacheEndpoint: cache.endpoint,
storageEndpoint: bucket.endpointUrl,
storageAccessKey: storageKeys.accessKey,
storageSecretKey: storageKeys.secretKey,
};
Using Component Resources
Create reusable infrastructure components:
// components/WebApp.ts
import * as pulumi from "@pulumi/pulumi";
import * as danubedata from "@danubedata/pulumi";
export interface WebAppArgs {
name: string;
environment: string;
sshPublicKey: pulumi.Input<string>;
vpsProfile?: string;
dbProfile?: string;
cacheProfile?: string;
enableBackups?: boolean;
}
export class WebApp extends pulumi.ComponentResource {
public readonly vps: danubedata.Vps;
public readonly database: danubedata.Database;
public readonly cache: danubedata.Cache;
public readonly bucket: danubedata.StorageBucket;
constructor(name: string, args: WebAppArgs, opts?: pulumi.ComponentResourceOptions) {
super("custom:resource:WebApp", name, {}, opts);
const childOpts = { parent: this };
// SSH Key
const sshKey = new danubedata.SshKey(`${name}-ssh`, {
name: `${args.name}-${args.environment}`,
publicKey: args.sshPublicKey,
}, childOpts);
// Database
this.database = new danubedata.Database(`${name}-db`, {
name: `${args.name}-${args.environment}-db`,
engine: "postgresql",
engineVersion: "16",
resourceProfile: args.dbProfile ?? "small",
databaseName: args.name.replace(/-/g, "_"),
datacenter: "fsn1",
}, childOpts);
// Cache
this.cache = new danubedata.Cache(`${name}-cache`, {
name: `${args.name}-${args.environment}-cache`,
cacheProvider: "redis",
resourceProfile: args.cacheProfile ?? "micro",
datacenter: "fsn1",
}, childOpts);
// Storage
this.bucket = new danubedata.StorageBucket(`${name}-storage`, {
name: `${args.name}-${args.environment}-assets`,
region: "fsn1",
}, childOpts);
// VPS
this.vps = new danubedata.Vps(`${name}-vps`, {
name: `${args.name}-${args.environment}`,
image: "ubuntu-24.04",
datacenter: "fsn1",
resourceProfile: args.vpsProfile ?? "small_shared",
authMethod: "ssh_key",
sshKeyId: sshKey.id,
enableBackups: args.enableBackups ?? false,
}, childOpts);
this.registerOutputs({
vpsIp: this.vps.publicIp,
dbEndpoint: this.database.endpoint,
cacheEndpoint: this.cache.endpoint,
});
}
}
// index.ts
import { WebApp } from "./components/WebApp";
const app = new WebApp("myapp", {
name: "myapp",
environment: "prod",
sshPublicKey: "ssh-ed25519 AAAA...",
vpsProfile: "medium_shared",
dbProfile: "medium",
enableBackups: true,
});
export const appIp = app.vps.publicIp;
export const dbEndpoint = app.database.endpoint;
Serverless Containers
Deploy scale-to-zero serverless containers:
// serverless.ts
import * as danubedata from "@danubedata/pulumi";
// Deploy from Docker image
const apiFromImage = new danubedata.Serverless("api", {
name: "api-service",
deploymentType: "image",
imageUrl: "ghcr.io/myorg/api:latest",
port: 8080,
minInstances: 0, // Scale to zero
maxInstances: 10,
memory: 512,
cpu: 0.5,
envVars: {
NODE_ENV: "production",
DATABASE_URL: database.endpoint,
REDIS_URL: cache.endpoint,
},
});
// Deploy from Git repository (with auto-build)
const webFromGit = new danubedata.Serverless("web", {
name: "web-app",
deploymentType: "git",
gitUrl: "https://github.com/myorg/web-app.git",
gitBranch: "main",
port: 3000,
minInstances: 1, // Always running
maxInstances: 5,
buildCommand: "npm run build",
startCommand: "npm start",
});
export const apiUrl = apiFromImage.url;
export const webUrl = webFromGit.url;
Using Data Sources
Query existing infrastructure:
// data-sources.ts
import * as danubedata from "@danubedata/pulumi";
// Get available VPS images
const images = danubedata.getVpsImages({});
images.then(result => {
console.log("Available images:", result.images);
});
// Get existing SSH keys
const existingKeys = danubedata.getSshKeys({});
// Get existing VPS instances
const existingVps = danubedata.getVpss({});
existingVps.then(result => {
result.instances.forEach(vps => {
console.log(`VPS: ${vps.name} - ${vps.publicIp}`);
});
});
// Get all databases
const databases = danubedata.getDatabases({});
Multi-Environment Setup
# Create environment stacks
pulumi stack init dev
pulumi stack init staging
pulumi stack init prod
# Configure each stack
pulumi config set --stack dev appName myapp
pulumi config set --stack dev adminIp 1.2.3.4/32
pulumi config set --stack prod appName myapp
pulumi config set --stack prod adminIp 1.2.3.4/32
// index.ts - Environment-aware configuration
import * as pulumi from "@pulumi/pulumi";
const stack = pulumi.getStack();
const profiles: Record<string, { vps: string; db: string; cache: string }> = {
dev: { vps: "nano_shared", db: "small", cache: "micro" },
staging: { vps: "small_shared", db: "small", cache: "small" },
prod: { vps: "medium_dedicated", db: "medium", cache: "medium" },
};
const profile = profiles[stack] ?? profiles.dev;
const vps = new danubedata.Vps("app", {
name: `myapp-${stack}`,
resourceProfile: profile.vps,
// ... other options
});
CI/CD with GitHub Actions
# .github/workflows/pulumi.yml
name: Pulumi Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
DANUBEDATA_API_TOKEN: ${{ secrets.DANUBEDATA_API_TOKEN }}
jobs:
preview:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- uses: pulumi/actions@v5
with:
command: preview
stack-name: dev
deploy:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- uses: pulumi/actions@v5
with:
command: up
stack-name: prod
Best Practices
1. Use Secrets for Sensitive Data
// Never hardcode secrets
const config = new pulumi.Config();
const dbPassword = config.requireSecret("dbPassword");
const apiKey = config.requireSecret("apiKey");
2. Tag All Resources
const defaultTags = {
environment: pulumi.getStack(),
project: pulumi.getProject(),
managedBy: "pulumi",
};
const vps = new danubedata.Vps("app", {
name: "app-server",
tags: defaultTags,
// ...
});
3. Use Transformations for Consistency
// Apply defaults to all resources
pulumi.runtime.registerStackTransformation((args) => {
if (args.type === "danubedata:index:Vps") {
args.props["enableBackups"] = args.props["enableBackups"] ?? true;
}
return { props: args.props, opts: args.opts };
});
Useful Commands
# Preview changes
pulumi preview
# Apply changes
pulumi up
# View current state
pulumi stack
# Export stack state
pulumi stack export
# View resource outputs
pulumi stack output
# Destroy all resources
pulumi destroy
# View resource details
pulumi stack output --json
Cost Estimation
| Resource | Profile | Monthly Cost |
|---|---|---|
| VPS (small_shared) | 2 vCPU, 4GB RAM | 8.99 |
| PostgreSQL (small) | 1 vCPU, 2GB RAM | 19.99 |
| Redis (micro) | 256MB RAM | 4.99 |
| Storage Bucket | 1TB included | 3.99 |
| Total | 37.96/month |
Get Started
Ready to manage your infrastructure as code?
Create your free DanubeData account
Then install the Pulumi provider:
npm install @danubedata/pulumi
Full documentation and examples are available in our GitHub repository.