Infrastructure as Code with Pulumi and .NET: DanubeData Complete Guide

Learn how to manage DanubeData cloud infrastructure using Pulumi and C#/.NET. Deploy VPS instances, databases, Redis cache, and serverless containers with strongly-typed infrastructure as code.

Adrian Silaghi

14 min read

976 views

pulumidotnetcsharpinfrastructure-as-codeiacdevopscloudautomation

For .NET developers, Pulumi offers a familiar experience using C# to define infrastructure. You get the full power of .NET—LINQ, async/await, dependency injection, and the rich ecosystem—applied to infrastructure as code. This guide shows you how to manage DanubeData infrastructure with idiomatic C#.

Why Pulumi with .NET?

C# and .NET are excellent for infrastructure as code:

  • Strong static typing catches errors at compile time
  • IntelliSense and code completion in Visual Studio/VS Code
  • Use async/await for complex provisioning workflows
  • Familiar patterns: dependency injection, interfaces, generics
  • Integration with existing .NET tooling and CI/CD

Getting Started

Prerequisites

  • .NET 8.0 SDK or later
  • Pulumi CLI
  • Visual Studio 2022, VS Code, or JetBrains Rider

Install Pulumi CLI

# macOS
brew install pulumi

# Linux
curl -fsSL https://get.pulumi.com | sh

# Windows
choco install pulumi
# or
winget install pulumi

Create a New Project

# Create a new directory
mkdir my-infrastructure && cd my-infrastructure

# Initialize a new C# project
pulumi new csharp

# Add the DanubeData provider package
dotnet add package DanubeData.Pulumi

Configure Authentication

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

Project Structure

A well-organized Pulumi .NET project:

my-infrastructure/
├── Pulumi.yaml                  # Project configuration
├── Pulumi.dev.yaml              # Development stack config
├── Pulumi.prod.yaml             # Production stack config
├── MyInfrastructure.csproj      # C# project file
├── Program.cs                   # Main entry point
└── Resources/
    ├── VpsResources.cs          # VPS resources
    ├── DatabaseResources.cs     # Database resources
    ├── CacheResources.cs        # Cache resources
    └── Components/
        └── WebAppComponent.cs   # Reusable components

Creating Your First VPS

// Program.cs
using Pulumi;
using DanubeData = DanubeData.Pulumi;

return await Deployment.RunAsync(() =>
{
    // Create an SSH key for authentication
    var sshKey = new DanubeData.SshKey("deploy-key", new()
    {
        Name = "deploy-key",
        PublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample... deploy@example.com"
    });

    // Create a VPS instance
    var webServer = new DanubeData.Vps("web-server", new()
    {
        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
    return new Dictionary<string, object?>
    {
        ["webServerIp"] = webServer.PublicIp,
        ["webServerIpv6"] = webServer.Ipv6Address
    };
});

Complete Web Application Stack

Here's a complete example using C# best practices:

// Program.cs
using Pulumi;
using DanubeData = DanubeData.Pulumi;
using System.Collections.Generic;

return await Deployment.RunAsync(() =>
{
    var config = new Config();
    var environment = Deployment.Instance.StackName;
    var appName = config.Require("appName");
    var adminIp = config.Require("adminIp");

    // Get profiles based on environment
    var profiles = GetResourceProfiles(environment);

    // SSH Key
    var sshKey = new DanubeData.SshKey("deploy-key", new()
    {
        Name = $"{appName}-{environment}-deploy",
        PublicKey = config.RequireSecret("sshPublicKey")
    });

    // PostgreSQL Database
    var database = new DanubeData.Database("app-db", new()
    {
        Name = $"{appName}-{environment}-db",
        Engine = "postgresql",
        EngineVersion = "16",
        ResourceProfile = profiles.Database,
        DatabaseName = appName.Replace("-", "_"),
        Datacenter = "fsn1",
        EnableSsl = true
    });

    // Redis Cache
    var cache = new DanubeData.Cache("app-cache", new()
    {
        Name = $"{appName}-{environment}-cache",
        CacheProvider = "redis",
        ResourceProfile = profiles.Cache,
        Datacenter = "fsn1"
    });

    // Storage Bucket
    var bucket = new DanubeData.StorageBucket("app-storage", new()
    {
        Name = $"{appName}-{environment}-assets",
        Region = "fsn1",
        VersioningEnabled = true
    });

    var storageKeys = new DanubeData.StorageAccessKey("app-storage-key", new()
    {
        Name = $"{appName}-{environment}-key"
    });

    // Application VPS
    var appServer = new DanubeData.Vps("app-server", new()
    {
        Name = $"{appName}-{environment}-app",
        Image = "ubuntu-24.04",
        Datacenter = "fsn1",
        ResourceProfile = profiles.Vps,
        AuthMethod = "ssh_key",
        SshKeyId = sshKey.Id,
        EnableIpv6 = true,
        EnableBackups = environment == "prod"
    });

    // Firewall Rules
    var webFirewall = new DanubeData.Firewall("web-firewall", new()
    {
        Name = $"{appName}-{environment}-web-fw",
        Rules = new[]
        {
            new DanubeData.Inputs.FirewallRuleArgs
            {
                Direction = "inbound",
                Protocol = "tcp",
                Ports = "80",
                SourceIps = new[] { "0.0.0.0/0", "::/0" },
                Description = "Allow HTTP"
            },
            new DanubeData.Inputs.FirewallRuleArgs
            {
                Direction = "inbound",
                Protocol = "tcp",
                Ports = "443",
                SourceIps = new[] { "0.0.0.0/0", "::/0" },
                Description = "Allow HTTPS"
            },
            new DanubeData.Inputs.FirewallRuleArgs
            {
                Direction = "inbound",
                Protocol = "tcp",
                Ports = "22",
                SourceIps = new[] { adminIp },
                Description = "SSH from admin only"
            }
        }
    });

    // Database Firewall
    var dbFirewall = new DanubeData.Firewall("db-firewall", new()
    {
        Name = $"{appName}-{environment}-db-fw",
        Rules = new[]
        {
            new DanubeData.Inputs.FirewallRuleArgs
            {
                Direction = "inbound",
                Protocol = "tcp",
                Ports = "5432",
                SourceIps = new InputList<string>
                {
                    appServer.PublicIp.Apply(ip => $"{ip}/32")
                },
                Description = "PostgreSQL from app server"
            }
        }
    });

    // Exports
    return new Dictionary<string, object?>
    {
        ["appServerIp"] = appServer.PublicIp,
        ["appServerIpv6"] = appServer.Ipv6Address,
        ["databaseEndpoint"] = database.Endpoint,
        ["databaseName"] = database.DatabaseName,
        ["cacheEndpoint"] = cache.Endpoint,
        ["storageEndpoint"] = bucket.EndpointUrl,
        ["storageAccessKey"] = storageKeys.AccessKey,
        ["storageSecretKey"] = storageKeys.SecretKey
    };
});

static ResourceProfiles GetResourceProfiles(string environment) => environment switch
{
    "prod" => new("medium_dedicated", "medium", "medium"),
    "staging" => new("small_shared", "small", "small"),
    _ => new("nano_shared", "small", "micro")
};

record ResourceProfiles(string Vps, string Database, string Cache);

Using Component Resources

Create reusable C# classes for complex infrastructure:

// Resources/Components/WebAppComponent.cs
using Pulumi;
using DanubeData = DanubeData.Pulumi;
using System.Collections.Generic;

namespace MyInfrastructure.Resources.Components;

public class WebAppArgs
{
    public required string Name { get; init; }
    public required string Environment { get; init; }
    public required Input<string> SshPublicKey { get; init; }
    public string VpsProfile { get; init; } = "small_shared";
    public string DbProfile { get; init; } = "small";
    public string CacheProfile { get; init; } = "micro";
    public bool EnableBackups { get; init; } = false;
}

public class WebApp : ComponentResource
{
    public Output<string> VpsIp { get; private set; } = null!;
    public Output<string> DatabaseEndpoint { get; private set; } = null!;
    public Output<string> CacheEndpoint { get; private set; } = null!;
    public Output<string> StorageEndpoint { get; private set; } = null!;

    public DanubeData.Vps Vps { get; private set; } = null!;
    public DanubeData.Database Database { get; private set; } = null!;
    public DanubeData.Cache Cache { get; private set; } = null!;
    public DanubeData.StorageBucket Bucket { get; private set; } = null!;

    public WebApp(string name, WebAppArgs args, ComponentResourceOptions? options = null)
        : base("custom:resource:WebApp", name, options)
    {
        var childOpts = new CustomResourceOptions { Parent = this };

        // SSH Key
        var sshKey = new DanubeData.SshKey($"{name}-ssh", new()
        {
            Name = $"{args.Name}-{args.Environment}",
            PublicKey = args.SshPublicKey
        }, childOpts);

        // Database
        Database = new DanubeData.Database($"{name}-db", new()
        {
            Name = $"{args.Name}-{args.Environment}-db",
            Engine = "postgresql",
            EngineVersion = "16",
            ResourceProfile = args.DbProfile,
            DatabaseName = args.Name.Replace("-", "_"),
            Datacenter = "fsn1"
        }, childOpts);

        // Cache
        Cache = new DanubeData.Cache($"{name}-cache", new()
        {
            Name = $"{args.Name}-{args.Environment}-cache",
            CacheProvider = "redis",
            ResourceProfile = args.CacheProfile,
            Datacenter = "fsn1"
        }, childOpts);

        // Storage
        Bucket = new DanubeData.StorageBucket($"{name}-storage", new()
        {
            Name = $"{args.Name}-{args.Environment}-assets",
            Region = "fsn1"
        }, childOpts);

        // VPS
        Vps = new DanubeData.Vps($"{name}-vps", new()
        {
            Name = $"{args.Name}-{args.Environment}",
            Image = "ubuntu-24.04",
            Datacenter = "fsn1",
            ResourceProfile = args.VpsProfile,
            AuthMethod = "ssh_key",
            SshKeyId = sshKey.Id,
            EnableBackups = args.EnableBackups
        }, childOpts);

        // Set outputs
        VpsIp = Vps.PublicIp;
        DatabaseEndpoint = Database.Endpoint;
        CacheEndpoint = Cache.Endpoint;
        StorageEndpoint = Bucket.EndpointUrl;

        RegisterOutputs(new Dictionary<string, object?>
        {
            ["vpsIp"] = VpsIp,
            ["databaseEndpoint"] = DatabaseEndpoint,
            ["cacheEndpoint"] = CacheEndpoint,
            ["storageEndpoint"] = StorageEndpoint
        });
    }
}
// Program.cs
using Pulumi;
using MyInfrastructure.Resources.Components;

return await Deployment.RunAsync(() =>
{
    var app = new WebApp("myapp", new()
    {
        Name = "myapp",
        Environment = "prod",
        SshPublicKey = "ssh-ed25519 AAAA...",
        VpsProfile = "medium_shared",
        DbProfile = "medium",
        EnableBackups = true
    });

    return new Dictionary<string, object?>
    {
        ["appIp"] = app.VpsIp,
        ["dbEndpoint"] = app.DatabaseEndpoint
    };
});

Creating Multiple Resources with LINQ

// Program.cs
using Pulumi;
using DanubeData = DanubeData.Pulumi;
using System.Linq;

return await Deployment.RunAsync(() =>
{
    var servers = new[]
    {
        new { Name = "web-1", Profile = "small_shared" },
        new { Name = "web-2", Profile = "small_shared" },
        new { Name = "api-1", Profile = "medium_shared" },
        new { Name = "worker-1", Profile = "medium_dedicated" }
    };

    // Create SSH key once
    var sshKey = new DanubeData.SshKey("shared-key", new()
    {
        Name = "shared-deploy-key",
        PublicKey = "ssh-ed25519 AAAA..."
    });

    // Create all servers using LINQ
    var vpsInstances = servers.Select(server =>
        new DanubeData.Vps(server.Name, new()
        {
            Name = server.Name,
            Image = "ubuntu-24.04",
            Datacenter = "fsn1",
            ResourceProfile = server.Profile,
            AuthMethod = "ssh_key",
            SshKeyId = sshKey.Id
        })
    ).ToArray();

    // Export all IPs
    return servers.Zip(vpsInstances).ToDictionary(
        pair => $"{pair.First.Name}Ip",
        pair => (object?)pair.Second.PublicIp
    );
});

Serverless Containers

// Resources/ServerlessResources.cs
using Pulumi;
using DanubeData = DanubeData.Pulumi;

namespace MyInfrastructure.Resources;

public static class ServerlessResources
{
    public static (DanubeData.Serverless Api, DanubeData.Serverless Web) Create(
        Output<string> databaseEndpoint,
        Output<string> cacheEndpoint)
    {
        // Deploy from Docker image
        var apiService = new DanubeData.Serverless("api", new()
        {
            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 = new InputMap<string>
            {
                ["NODE_ENV"] = "production",
                ["DATABASE_URL"] = databaseEndpoint,
                ["REDIS_URL"] = cacheEndpoint
            }
        });

        // Deploy from Git repository
        var webApp = new DanubeData.Serverless("web", new()
        {
            Name = "web-app",
            DeploymentType = "git",
            GitUrl = "https://github.com/myorg/web-app.git",
            GitBranch = "main",
            Port = 3000,
            MinInstances = 1,
            MaxInstances = 5,
            BuildCommand = "npm run build",
            StartCommand = "npm start"
        });

        return (apiService, webApp);
    }
}

Using Data Sources

// DataSources.cs
using Pulumi;
using DanubeData = DanubeData.Pulumi;

namespace MyInfrastructure;

public static class DataSources
{
    public static async Task<IEnumerable<string>> GetAvailableImagesAsync()
    {
        var images = await DanubeData.GetVpsImages.InvokeAsync();
        return images.Images.Select(i => i.Slug);
    }

    public static Output<IEnumerable<DanubeData.Outputs.GetVpssInstanceResult>> GetExistingVps()
    {
        return DanubeData.GetVpss.Invoke().Apply(result => result.Instances);
    }

    public static Output<IEnumerable<DanubeData.Outputs.GetDatabasesInstanceResult>> GetExistingDatabases()
    {
        return DanubeData.GetDatabases.Invoke().Apply(result => result.Instances);
    }
}

Dependency Injection Pattern

// Infrastructure/IInfrastructureFactory.cs
using Pulumi;

namespace MyInfrastructure.Infrastructure;

public interface IInfrastructureFactory
{
    DanubeData.Vps CreateVps(string name, VpsOptions options);
    DanubeData.Database CreateDatabase(string name, DatabaseOptions options);
    DanubeData.Cache CreateCache(string name, CacheOptions options);
}

public record VpsOptions(
    string Image,
    string Profile,
    Input<string> SshKeyId,
    bool EnableBackups = false
);

public record DatabaseOptions(
    string Engine,
    string Profile,
    string DatabaseName
);

public record CacheOptions(
    string Provider,
    string Profile
);

public class InfrastructureFactory : IInfrastructureFactory
{
    private readonly string _environment;
    private readonly string _datacenter;
    private readonly ComponentResource? _parent;

    public InfrastructureFactory(string environment, string datacenter = "fsn1",
        ComponentResource? parent = null)
    {
        _environment = environment;
        _datacenter = datacenter;
        _parent = parent;
    }

    public DanubeData.Vps CreateVps(string name, VpsOptions options)
    {
        return new DanubeData.Vps(name, new()
        {
            Name = $"{name}-{_environment}",
            Image = options.Image,
            Datacenter = _datacenter,
            ResourceProfile = options.Profile,
            AuthMethod = "ssh_key",
            SshKeyId = options.SshKeyId,
            EnableBackups = options.EnableBackups
        }, new CustomResourceOptions { Parent = _parent });
    }

    public DanubeData.Database CreateDatabase(string name, DatabaseOptions options)
    {
        return new DanubeData.Database(name, new()
        {
            Name = $"{name}-{_environment}",
            Engine = options.Engine,
            ResourceProfile = options.Profile,
            DatabaseName = options.DatabaseName,
            Datacenter = _datacenter
        }, new CustomResourceOptions { Parent = _parent });
    }

    public DanubeData.Cache CreateCache(string name, CacheOptions options)
    {
        return new DanubeData.Cache(name, new()
        {
            Name = $"{name}-{_environment}",
            CacheProvider = options.Provider,
            ResourceProfile = options.Profile,
            Datacenter = _datacenter
        }, new CustomResourceOptions { Parent = _parent });
    }
}

Async/Await Patterns

// AsyncInfrastructure.cs
using Pulumi;
using DanubeData = DanubeData.Pulumi;

namespace MyInfrastructure;

public static class AsyncInfrastructure
{
    public static async Task<Dictionary<string, object?>> CreateAsync()
    {
        // Get available images first
        var images = await DanubeData.GetVpsImages.InvokeAsync();
        var latestUbuntu = images.Images
            .Where(i => i.Slug.StartsWith("ubuntu"))
            .OrderByDescending(i => i.Slug)
            .FirstOrDefault()?.Slug ?? "ubuntu-24.04";

        // Create SSH key
        var sshKey = new DanubeData.SshKey("key", new()
        {
            Name = "async-key",
            PublicKey = "ssh-ed25519 AAAA..."
        });

        // Create VPS with latest Ubuntu
        var vps = new DanubeData.Vps("server", new()
        {
            Name = "async-server",
            Image = latestUbuntu,
            Datacenter = "fsn1",
            ResourceProfile = "small_shared",
            AuthMethod = "ssh_key",
            SshKeyId = sshKey.Id
        });

        return new Dictionary<string, object?>
        {
            ["image"] = latestUbuntu,
            ["ip"] = vps.PublicIp
        };
    }
}

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-dotnet@v4
        with:
          dotnet-version: "8.0.x"
      - run: dotnet restore
      - 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-dotnet@v4
        with:
          dotnet-version: "8.0.x"
      - run: dotnet restore
      - uses: pulumi/actions@v5
        with:
          command: up
          stack-name: prod

Testing Infrastructure Code

// Tests/InfrastructureTests.cs
using Pulumi;
using Pulumi.Testing;
using DanubeData = DanubeData.Pulumi;
using Xunit;
using System.Collections.Immutable;

namespace MyInfrastructure.Tests;

public class InfrastructureTests
{
    [Fact]
    public async Task VpsHasCorrectImage()
    {
        var mocks = new TestMocks();
        var resources = await Testing.RunAsync<MyStack>(mocks);

        var vps = resources.OfType<DanubeData.Vps>().FirstOrDefault();
        Assert.NotNull(vps);

        var image = await vps.Image.GetValueAsync();
        Assert.Equal("ubuntu-24.04", image);
    }
}

public class TestMocks : IMocks
{
    public Task<(string? id, object state)> NewResourceAsync(MockResourceArgs args)
    {
        return Task.FromResult<(string?, object)>((args.Name + "_id", args.Inputs));
    }

    public Task<object> CallAsync(MockCallArgs args)
    {
        return Task.FromResult<object>(args.Args);
    }
}

Project File

<!-- MyInfrastructure.csproj -->
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Pulumi" Version="3.*" />
    <PackageReference Include="DanubeData.Pulumi" Version="1.*" />
  </ItemGroup>

</Project>

Useful Commands

# Build the project
dotnet 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

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 C#?

Create your free DanubeData account

Then install the Pulumi provider:

dotnet add package DanubeData.Pulumi

Full documentation and examples are available in our GitHub repository.

Browse all articles
Tutorials7 min read

Raport zilnic de stoc WooCommerce cu n8n

Importă un workflow n8n pentru raportul de stoc WooCommerce: paginare, variații, praguri și controlul retrimiterilor, cu acces doar de citire în magazin.

Adrian Silaghi55 views

Ready to get started?

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