BlogTutorialsInfrastructure as Code with Pulumi and Go: DanubeData Complete Guide

Infrastructure as Code with Pulumi and Go: DanubeData Complete Guide

Adrian Silaghi
Adrian Silaghi
January 30, 2026
14 min read
28 views
#pulumi #go #golang #infrastructure-as-code #iac #devops #cloud #automation

Go developers appreciate Pulumi for its type safety, speed, and the ability to use the same language for both application and infrastructure code. This guide shows you how to manage DanubeData infrastructure with idiomatic Go.

Why Pulumi with Go?

Go is an excellent choice for infrastructure as code:

  • Compiled language catches errors before deployment
  • Strong static typing prevents configuration mistakes
  • Fast compilation and execution
  • Excellent concurrency for parallel resource operations
  • Single binary deployment—no runtime dependencies

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 Go project
pulumi new go

# Get the DanubeData provider
go get github.com/AdrianSilaghi/pulumi-danubedata/sdk/go/danubedata

Configure Authentication

# Set your DanubeData API token
pulumi config set danubedata:apiToken your-api-token --secret

Project Structure

A well-organized Pulumi Go project:

my-infrastructure/
├── Pulumi.yaml              # Project configuration
├── Pulumi.dev.yaml          # Development stack config
├── Pulumi.prod.yaml         # Production stack config
├── main.go                  # Main entry point
├── go.mod                   # Go module file
├── go.sum                   # Go dependencies checksum
└── pkg/
    ├── vps/                 # VPS resources
    │   └── vps.go
    ├── database/            # Database resources
    │   └── database.go
    ├── cache/               # Cache resources
    │   └── cache.go
    └── components/          # Reusable components
        └── webapp.go

Creating Your First VPS

// main.go
package main

import (
	"github.com/AdrianSilaghi/pulumi-danubedata/sdk/go/danubedata"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		// Create an SSH key for authentication
		sshKey, err := danubedata.NewSshKey(ctx, "deploy-key", &danubedata.SshKeyArgs{
			Name:      pulumi.String("deploy-key"),
			PublicKey: pulumi.String("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample... deploy@example.com"),
		})
		if err != nil {
			return err
		}

		// Create a VPS instance
		webServer, err := danubedata.NewVps(ctx, "web-server", &danubedata.VpsArgs{
			Name:            pulumi.String("web-server"),
			Image:           pulumi.String("ubuntu-24.04"),
			Datacenter:      pulumi.String("fsn1"),
			ResourceProfile: pulumi.String("small_shared"),
			AuthMethod:      pulumi.String("ssh_key"),
			SshKeyId:        sshKey.ID(),
			EnableIpv6:      pulumi.Bool(true),
			EnableBackups:   pulumi.Bool(true),
		})
		if err != nil {
			return err
		}

		// Export the VPS public IP
		ctx.Export("webServerIp", webServer.PublicIp)
		ctx.Export("webServerIpv6", webServer.Ipv6Address)

		return nil
	})
}

Complete Web Application Stack

Here's a complete example using Go best practices:

// main.go
package main

import (
	"fmt"

	"github.com/AdrianSilaghi/pulumi-danubedata/sdk/go/danubedata"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
)

// ResourceProfiles defines resource profiles for different environments
type ResourceProfiles struct {
	VPS      string
	Database string
	Cache    string
}

func getProfiles(env string) ResourceProfiles {
	profiles := map[string]ResourceProfiles{
		"dev":     {VPS: "nano_shared", Database: "small", Cache: "micro"},
		"staging": {VPS: "small_shared", Database: "small", Cache: "small"},
		"prod":    {VPS: "medium_dedicated", Database: "medium", Cache: "medium"},
	}
	if p, ok := profiles[env]; ok {
		return p
	}
	return profiles["dev"]
}

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		cfg := config.New(ctx, "")
		appName := cfg.Require("appName")
		adminIP := cfg.Require("adminIp")
		environment := ctx.Stack()
		profiles := getProfiles(environment)

		// SSH Key
		sshKey, err := danubedata.NewSshKey(ctx, "deploy-key", &danubedata.SshKeyArgs{
			Name:      pulumi.Sprintf("%s-%s-deploy", appName, environment),
			PublicKey: cfg.RequireSecret("sshPublicKey"),
		})
		if err != nil {
			return err
		}

		// PostgreSQL Database
		database, err := danubedata.NewDatabase(ctx, "app-db", &danubedata.DatabaseArgs{
			Name:            pulumi.Sprintf("%s-%s-db", appName, environment),
			Engine:          pulumi.String("postgresql"),
			EngineVersion:   pulumi.String("16"),
			ResourceProfile: pulumi.String(profiles.Database),
			DatabaseName:    pulumi.String(appName),
			Datacenter:      pulumi.String("fsn1"),
			EnableSsl:       pulumi.Bool(true),
		})
		if err != nil {
			return err
		}

		// Redis Cache
		cache, err := danubedata.NewCache(ctx, "app-cache", &danubedata.CacheArgs{
			Name:            pulumi.Sprintf("%s-%s-cache", appName, environment),
			CacheProvider:   pulumi.String("redis"),
			ResourceProfile: pulumi.String(profiles.Cache),
			Datacenter:      pulumi.String("fsn1"),
		})
		if err != nil {
			return err
		}

		// Storage Bucket
		bucket, err := danubedata.NewStorageBucket(ctx, "app-storage", &danubedata.StorageBucketArgs{
			Name:              pulumi.Sprintf("%s-%s-assets", appName, environment),
			Region:            pulumi.String("fsn1"),
			VersioningEnabled: pulumi.Bool(true),
		})
		if err != nil {
			return err
		}

		storageKeys, err := danubedata.NewStorageAccessKey(ctx, "app-storage-key", &danubedata.StorageAccessKeyArgs{
			Name: pulumi.Sprintf("%s-%s-key", appName, environment),
		})
		if err != nil {
			return err
		}

		// Application VPS
		appServer, err := danubedata.NewVps(ctx, "app-server", &danubedata.VpsArgs{
			Name:            pulumi.Sprintf("%s-%s-app", appName, environment),
			Image:           pulumi.String("ubuntu-24.04"),
			Datacenter:      pulumi.String("fsn1"),
			ResourceProfile: pulumi.String(profiles.VPS),
			AuthMethod:      pulumi.String("ssh_key"),
			SshKeyId:        sshKey.ID(),
			EnableIpv6:      pulumi.Bool(true),
			EnableBackups:   pulumi.Bool(environment == "prod"),
		})
		if err != nil {
			return err
		}

		// Firewall Rules
		_, err = danubedata.NewFirewall(ctx, "web-firewall", &danubedata.FirewallArgs{
			Name: pulumi.Sprintf("%s-%s-web-fw", appName, environment),
			Rules: danubedata.FirewallRuleArray{
				&danubedata.FirewallRuleArgs{
					Direction:   pulumi.String("inbound"),
					Protocol:    pulumi.String("tcp"),
					Ports:       pulumi.String("80"),
					SourceIps:   pulumi.StringArray{pulumi.String("0.0.0.0/0"), pulumi.String("::/0")},
					Description: pulumi.String("Allow HTTP"),
				},
				&danubedata.FirewallRuleArgs{
					Direction:   pulumi.String("inbound"),
					Protocol:    pulumi.String("tcp"),
					Ports:       pulumi.String("443"),
					SourceIps:   pulumi.StringArray{pulumi.String("0.0.0.0/0"), pulumi.String("::/0")},
					Description: pulumi.String("Allow HTTPS"),
				},
				&danubedata.FirewallRuleArgs{
					Direction:   pulumi.String("inbound"),
					Protocol:    pulumi.String("tcp"),
					Ports:       pulumi.String("22"),
					SourceIps:   pulumi.StringArray{pulumi.String(adminIP)},
					Description: pulumi.String("SSH from admin only"),
				},
			},
		})
		if err != nil {
			return err
		}

		// Database Firewall
		_, err = danubedata.NewFirewall(ctx, "db-firewall", &danubedata.FirewallArgs{
			Name: pulumi.Sprintf("%s-%s-db-fw", appName, environment),
			Rules: danubedata.FirewallRuleArray{
				&danubedata.FirewallRuleArgs{
					Direction: pulumi.String("inbound"),
					Protocol:  pulumi.String("tcp"),
					Ports:     pulumi.String("5432"),
					SourceIps: pulumi.StringArray{
						appServer.PublicIp.ApplyT(func(ip string) string {
							return fmt.Sprintf("%s/32", ip)
						}).(pulumi.StringOutput),
					},
					Description: pulumi.String("PostgreSQL from app server"),
				},
			},
		})
		if err != nil {
			return err
		}

		// Exports
		ctx.Export("appServerIp", appServer.PublicIp)
		ctx.Export("appServerIpv6", appServer.Ipv6Address)
		ctx.Export("databaseEndpoint", database.Endpoint)
		ctx.Export("databaseName", database.DatabaseName)
		ctx.Export("cacheEndpoint", cache.Endpoint)
		ctx.Export("storageEndpoint", bucket.EndpointUrl)
		ctx.Export("storageAccessKey", storageKeys.AccessKey)
		ctx.Export("storageSecretKey", storageKeys.SecretKey)

		return nil
	})
}

Using Component Resources

Create reusable Go structs for complex infrastructure:

// pkg/components/webapp.go
package components

import (
	"github.com/AdrianSilaghi/pulumi-danubedata/sdk/go/danubedata"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

// WebAppArgs defines arguments for creating a WebApp component
type WebAppArgs struct {
	Name          string
	Environment   string
	SSHPublicKey  pulumi.StringInput
	VPSProfile    string
	DBProfile     string
	CacheProfile  string
	EnableBackups bool
}

// WebApp is a reusable component for a complete web application stack
type WebApp struct {
	pulumi.ResourceState

	VPS      *danubedata.Vps
	Database *danubedata.Database
	Cache    *danubedata.Cache
	Bucket   *danubedata.StorageBucket
}

// NewWebApp creates a new WebApp component
func NewWebApp(ctx *pulumi.Context, name string, args *WebAppArgs,
	opts ...pulumi.ResourceOption) (*WebApp, error) {

	component := &WebApp{}
	err := ctx.RegisterComponentResource("custom:resource:WebApp", name, component, opts...)
	if err != nil {
		return nil, err
	}

	childOpts := append(opts, pulumi.Parent(component))

	// Set defaults
	if args.VPSProfile == "" {
		args.VPSProfile = "small_shared"
	}
	if args.DBProfile == "" {
		args.DBProfile = "small"
	}
	if args.CacheProfile == "" {
		args.CacheProfile = "micro"
	}

	// SSH Key
	sshKey, err := danubedata.NewSshKey(ctx, name+"-ssh", &danubedata.SshKeyArgs{
		Name:      pulumi.Sprintf("%s-%s", args.Name, args.Environment),
		PublicKey: args.SSHPublicKey,
	}, childOpts...)
	if err != nil {
		return nil, err
	}

	// Database
	component.Database, err = danubedata.NewDatabase(ctx, name+"-db", &danubedata.DatabaseArgs{
		Name:            pulumi.Sprintf("%s-%s-db", args.Name, args.Environment),
		Engine:          pulumi.String("postgresql"),
		EngineVersion:   pulumi.String("16"),
		ResourceProfile: pulumi.String(args.DBProfile),
		DatabaseName:    pulumi.String(args.Name),
		Datacenter:      pulumi.String("fsn1"),
	}, childOpts...)
	if err != nil {
		return nil, err
	}

	// Cache
	component.Cache, err = danubedata.NewCache(ctx, name+"-cache", &danubedata.CacheArgs{
		Name:            pulumi.Sprintf("%s-%s-cache", args.Name, args.Environment),
		CacheProvider:   pulumi.String("redis"),
		ResourceProfile: pulumi.String(args.CacheProfile),
		Datacenter:      pulumi.String("fsn1"),
	}, childOpts...)
	if err != nil {
		return nil, err
	}

	// Storage
	component.Bucket, err = danubedata.NewStorageBucket(ctx, name+"-storage", &danubedata.StorageBucketArgs{
		Name:   pulumi.Sprintf("%s-%s-assets", args.Name, args.Environment),
		Region: pulumi.String("fsn1"),
	}, childOpts...)
	if err != nil {
		return nil, err
	}

	// VPS
	component.VPS, err = danubedata.NewVps(ctx, name+"-vps", &danubedata.VpsArgs{
		Name:            pulumi.Sprintf("%s-%s", args.Name, args.Environment),
		Image:           pulumi.String("ubuntu-24.04"),
		Datacenter:      pulumi.String("fsn1"),
		ResourceProfile: pulumi.String(args.VPSProfile),
		AuthMethod:      pulumi.String("ssh_key"),
		SshKeyId:        sshKey.ID(),
		EnableBackups:   pulumi.Bool(args.EnableBackups),
	}, childOpts...)
	if err != nil {
		return nil, err
	}

	ctx.RegisterResourceOutputs(component, pulumi.Map{
		"vpsIp":         component.VPS.PublicIp,
		"dbEndpoint":    component.Database.Endpoint,
		"cacheEndpoint": component.Cache.Endpoint,
	})

	return component, nil
}
// main.go
package main

import (
	"my-infrastructure/pkg/components"

	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		app, err := components.NewWebApp(ctx, "myapp", &components.WebAppArgs{
			Name:          "myapp",
			Environment:   "prod",
			SSHPublicKey:  pulumi.String("ssh-ed25519 AAAA..."),
			VPSProfile:    "medium_shared",
			DBProfile:     "medium",
			EnableBackups: true,
		})
		if err != nil {
			return err
		}

		ctx.Export("appIp", app.VPS.PublicIp)
		ctx.Export("dbEndpoint", app.Database.Endpoint)

		return nil
	})
}

Creating Multiple Resources with Loops

// main.go
package main

import (
	"github.com/AdrianSilaghi/pulumi-danubedata/sdk/go/danubedata"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

type ServerConfig struct {
	Name    string
	Profile string
}

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		servers := []ServerConfig{
			{Name: "web-1", Profile: "small_shared"},
			{Name: "web-2", Profile: "small_shared"},
			{Name: "api-1", Profile: "medium_shared"},
			{Name: "worker-1", Profile: "medium_dedicated"},
		}

		// Create SSH key once
		sshKey, err := danubedata.NewSshKey(ctx, "shared-key", &danubedata.SshKeyArgs{
			Name:      pulumi.String("shared-deploy-key"),
			PublicKey: pulumi.String("ssh-ed25519 AAAA..."),
		})
		if err != nil {
			return err
		}

		// Create all servers
		for _, server := range servers {
			vps, err := danubedata.NewVps(ctx, server.Name, &danubedata.VpsArgs{
				Name:            pulumi.String(server.Name),
				Image:           pulumi.String("ubuntu-24.04"),
				Datacenter:      pulumi.String("fsn1"),
				ResourceProfile: pulumi.String(server.Profile),
				AuthMethod:      pulumi.String("ssh_key"),
				SshKeyId:        sshKey.ID(),
			})
			if err != nil {
				return err
			}

			ctx.Export(server.Name+"Ip", vps.PublicIp)
		}

		return nil
	})
}

Serverless Containers

// serverless.go
package main

import (
	"github.com/AdrianSilaghi/pulumi-danubedata/sdk/go/danubedata"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func deployServerless(ctx *pulumi.Context) error {
	// Deploy from Docker image
	apiService, err := danubedata.NewServerless(ctx, "api", &danubedata.ServerlessArgs{
		Name:           pulumi.String("api-service"),
		DeploymentType: pulumi.String("image"),
		ImageUrl:       pulumi.String("ghcr.io/myorg/api:latest"),
		Port:           pulumi.Int(8080),
		MinInstances:   pulumi.Int(0), // Scale to zero
		MaxInstances:   pulumi.Int(10),
		Memory:         pulumi.Int(512),
		Cpu:            pulumi.Float64(0.5),
		EnvVars: pulumi.StringMap{
			"NODE_ENV": pulumi.String("production"),
		},
	})
	if err != nil {
		return err
	}

	// Deploy from Git repository
	webApp, err := danubedata.NewServerless(ctx, "web", &danubedata.ServerlessArgs{
		Name:           pulumi.String("web-app"),
		DeploymentType: pulumi.String("git"),
		GitUrl:         pulumi.String("https://github.com/myorg/web-app.git"),
		GitBranch:      pulumi.String("main"),
		Port:           pulumi.Int(3000),
		MinInstances:   pulumi.Int(1),
		MaxInstances:   pulumi.Int(5),
		BuildCommand:   pulumi.String("npm run build"),
		StartCommand:   pulumi.String("npm start"),
	})
	if err != nil {
		return err
	}

	ctx.Export("apiUrl", apiService.Url)
	ctx.Export("webUrl", webApp.Url)

	return nil
}

Using Data Sources

// data_sources.go
package main

import (
	"github.com/AdrianSilaghi/pulumi-danubedata/sdk/go/danubedata"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func queryDataSources(ctx *pulumi.Context) error {
	// Get available VPS images
	images, err := danubedata.GetVpsImages(ctx, nil)
	if err != nil {
		return err
	}
	for _, image := range images.Images {
		ctx.Log.Info(image.Name, nil)
	}

	// Get existing SSH keys
	keys, err := danubedata.GetSshKeys(ctx, nil)
	if err != nil {
		return err
	}
	_ = keys

	// Get existing VPS instances
	vpsList, err := danubedata.GetVpss(ctx, nil)
	if err != nil {
		return err
	}
	for _, vps := range vpsList.Instances {
		ctx.Log.Info(vps.Name+" - "+vps.PublicIp, nil)
	}

	return nil
}

Error Handling Patterns

// error_handling.go
package main

import (
	"fmt"

	"github.com/AdrianSilaghi/pulumi-danubedata/sdk/go/danubedata"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

// CreateInfrastructureError wraps infrastructure creation errors
type CreateInfrastructureError struct {
	Resource string
	Err      error
}

func (e *CreateInfrastructureError) Error() string {
	return fmt.Sprintf("failed to create %s: %v", e.Resource, e.Err)
}

func createWithErrorHandling(ctx *pulumi.Context) error {
	sshKey, err := danubedata.NewSshKey(ctx, "key", &danubedata.SshKeyArgs{
		Name:      pulumi.String("key"),
		PublicKey: pulumi.String("ssh-ed25519 AAAA..."),
	})
	if err != nil {
		return &CreateInfrastructureError{Resource: "SSH Key", Err: err}
	}

	_, err = danubedata.NewVps(ctx, "server", &danubedata.VpsArgs{
		Name:            pulumi.String("server"),
		Image:           pulumi.String("ubuntu-24.04"),
		Datacenter:      pulumi.String("fsn1"),
		ResourceProfile: pulumi.String("small_shared"),
		AuthMethod:      pulumi.String("ssh_key"),
		SshKeyId:        sshKey.ID(),
	})
	if err != nil {
		return &CreateInfrastructureError{Resource: "VPS", Err: err}
	}

	return nil
}

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-go@v5
        with:
          go-version: "1.22"
      - run: go mod download
      - 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-go@v5
        with:
          go-version: "1.22"
      - run: go mod download
      - uses: pulumi/actions@v5
        with:
          command: up
          stack-name: prod

Testing Infrastructure Code

// main_test.go
package main

import (
	"sync"
	"testing"

	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
	"github.com/stretchr/testify/assert"
)

type mocks int

func (mocks) NewResource(args pulumi.MockResourceArgs) (string, resource.PropertyMap, error) {
	return args.Name + "_id", args.Inputs, nil
}

func (mocks) Call(args pulumi.MockCallArgs) (resource.PropertyMap, error) {
	return args.Args, nil
}

func TestInfrastructure(t *testing.T) {
	err := pulumi.RunErr(func(ctx *pulumi.Context) error {
		// Create your infrastructure here
		// ...
		return nil
	}, pulumi.WithMocks("project", "stack", mocks(0)))

	assert.NoError(t, err)
}

Useful Commands

# Build the project
go build

# Preview changes
pulumi preview

# Apply changes
pulumi up

# View outputs
pulumi stack output

# Destroy resources
pulumi destroy

# Import existing resource
pulumi import danubedata:index:Vps my-vps existing-vps-id

Go Module File

// go.mod
module my-infrastructure

go 1.22

require (
    github.com/AdrianSilaghi/pulumi-danubedata/sdk/go/danubedata v1.0.0
    github.com/pulumi/pulumi/sdk/v3 v3.100.0
)

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 with Go?

Create your free DanubeData account

Then install the Pulumi provider:

go get github.com/AdrianSilaghi/pulumi-danubedata/sdk/go/danubedata

Full documentation and examples are available in our GitHub repository.

Share this article

Ready to Get Started?

Deploy your infrastructure in minutes with DanubeData's managed services.