Skip to main content

AWS CloudFormation with C# — Tutorial & Examples

Learn AWS CloudFormation with C# to automate cloud infrastructure. Step-by-step tutorial with code examples using AWS CDK and SDK for .NET.

AWS CloudFormation with C# — Automate Your Cloud Infrastructure

AWS CloudFormation with C# lets you define, deploy, and manage your entire cloud infrastructure using the language you already know. Instead of clicking through the AWS Console or writing JSON/YAML templates by hand, you can use strongly-typed C# code to provision S3 buckets, Lambda functions, DynamoDB tables, and hundreds of other AWS resources — all with IntelliSense, compile-time checks, and the full power of .NET.

In this tutorial, you will learn two powerful approaches: using the AWS CDK (Cloud Development Kit) to define infrastructure as C# code, and using the AWS SDK for .NET to manage CloudFormation stacks programmatically. By the end, you will be able to automate your AWS deployments entirely from C#.

Why Use C# for AWS CloudFormation?

If you are a .NET developer, writing infrastructure code in C# gives you several advantages over raw YAML or JSON templates:

  • Type safety — Catch configuration errors at compile time, not during a failed deployment at 2 AM.
  • Code reuse — Create reusable constructs, share them via NuGet packages, and apply the same patterns across projects.
  • IDE support — Full IntelliSense, refactoring, and debugging in Visual Studio or Rider.
  • Testability — Write unit tests for your infrastructure using xUnit or NUnit, just like application code.
  • Familiar patterns — Use loops, conditionals, and abstractions instead of CloudFormation's limited intrinsic functions.

There are two main ways to work with CloudFormation from C#. The AWS CDK is a framework for defining cloud resources as code — it generates CloudFormation templates under the hood. The AWS SDK for .NET gives you programmatic control over CloudFormation stacks — creating, updating, deleting, and monitoring them from your applications. Most real-world projects use both.

Getting Started with AWS CDK in C#

Prerequisites

Before you begin, make sure you have the following installed:

  • .NET 8 SDK or later
  • Node.js 18+ (the CDK CLI runs on Node)
  • AWS CLI configured with your credentials
  • AWS CDK CLI: npm install -g aws-cdk

Creating Your First CDK Project

Scaffold a new CDK project using the CLI:

// Terminal commands:
// mkdir MyCdkApp && cd MyCdkApp
// cdk init app --language csharp

// This generates a solution with the following structure:
// MyCdkApp/
//   src/
//     MyCdkApp/
//       MyCdkAppStack.cs    <-- your infrastructure goes here
//       Program.cs
//   cdk.json

The generated Program.cs is the entry point. It creates an App and instantiates your stack:

using Amazon.CDK;

namespace MyCdkApp
{
    sealed class Program
    {
        public static void Main(string[] args)
        {
            var app = new App();

            new MyCdkAppStack(app, "MyCdkAppStack", new StackProps
            {
                Env = new Amazon.CDK.Environment
                {
                    Account = "123456789012",
                    Region = "us-east-1"
                }
            });

            app.Synth();
        }
    }
}

Defining AWS Resources in C#

Here is a practical example that creates an S3 bucket, a DynamoDB table, and a Lambda function — a common pattern for serverless applications:

using Amazon.CDK;
using Amazon.CDK.AWS.S3;
using Amazon.CDK.AWS.DynamoDB;
using Amazon.CDK.AWS.Lambda;
using Constructs;

namespace MyCdkApp
{
    public class MyCdkAppStack : Stack
    {
        public MyCdkAppStack(Construct scope, string id, IStackProps props = null)
            : base(scope, id, props)
        {
            // S3 bucket with versioning and encryption
            var bucket = new Bucket(this, "DataBucket", new BucketProps
            {
                BucketName = "my-app-data-bucket",
                Versioned = true,
                Encryption = BucketEncryption.S3_MANAGED,
                RemovalPolicy = RemovalPolicy.DESTROY,
                AutoDeleteObjects = true
            });

            // DynamoDB table with on-demand capacity
            var table = new Table(this, "OrdersTable", new TableProps
            {
                TableName = "Orders",
                PartitionKey = new Attribute
                {
                    Name = "OrderId",
                    Type = AttributeType.STRING
                },
                SortKey = new Attribute
                {
                    Name = "CreatedAt",
                    Type = AttributeType.STRING
                },
                BillingMode = BillingMode.PAY_PER_REQUEST,
                RemovalPolicy = RemovalPolicy.DESTROY
            });

            // Lambda function
            var handler = new Function(this, "OrderHandler", new FunctionProps
            {
                Runtime = Runtime.DOTNET_8,
                Code = Code.FromAsset("src/OrderHandler/publish"),
                Handler = "OrderHandler::OrderHandler.Function::FunctionHandler",
                MemorySize = 512,
                Timeout = Duration.Seconds(30),
                Environment = new Dictionary<string, string>
                {
                    ["TABLE_NAME"] = table.TableName,
                    ["BUCKET_NAME"] = bucket.BucketName
                }
            });

            // Grant the Lambda function permissions
            table.GrantReadWriteData(handler);
            bucket.GrantReadWrite(handler);

            // Output the bucket name
            new CfnOutput(this, "BucketArn", new CfnOutputProps
            {
                Value = bucket.BucketArn,
                Description = "The ARN of the data bucket"
            });
        }
    }
}

Notice how the CDK handles IAM permissions for you. The GrantReadWriteData and GrantReadWrite calls automatically create the correct IAM policies. In raw CloudFormation, you would need to write dozens of lines of YAML to achieve the same thing.

Deploying Your Stack

Deploy with a single command:

// Terminal commands:
// cdk synth       — generates the CloudFormation template (good for review)
// cdk diff        — shows what will change before deploying
// cdk deploy      — deploys the stack to AWS
// cdk destroy     — tears down all resources

Always run cdk diff before deploying. It shows you exactly what resources will be created, updated, or deleted — preventing surprises in production.

Managing CloudFormation Stacks with the AWS SDK for .NET

While the CDK is ideal for defining infrastructure, you often need to manage stacks programmatically — from a CI/CD pipeline, an admin dashboard, or a deployment tool. The AWS SDK for .NET gives you full control over CloudFormation operations from C# code.

Installing the SDK

// NuGet packages:
// dotnet add package AWSSDK.CloudFormation
// dotnet add package AWSSDK.Extensions.NETCore.Setup  (for DI integration)

Creating a Stack Programmatically

This example reads a CloudFormation template and creates a stack:

using Amazon.CloudFormation;
using Amazon.CloudFormation.Model;

public class CloudFormationService
{
    private readonly IAmazonCloudFormation _cfnClient;

    public CloudFormationService(IAmazonCloudFormation cfnClient)
    {
        _cfnClient = cfnClient;
    }

    public async Task<string> CreateStackAsync(
        string stackName,
        string templateBody,
        Dictionary<string, string> parameters)
    {
        var request = new CreateStackRequest
        {
            StackName = stackName,
            TemplateBody = templateBody,
            Parameters = parameters.Select(kvp => new Parameter
            {
                ParameterKey = kvp.Key,
                ParameterValue = kvp.Value
            }).ToList(),
            Capabilities = new List<string>
            {
                "CAPABILITY_IAM",
                "CAPABILITY_NAMED_IAM"
            },
            Tags = new List<Tag>
            {
                new Tag { Key = "Environment", Value = "Production" },
                new Tag { Key = "ManagedBy", Value = "CSharpAutomation" }
            }
        };

        var response = await _cfnClient.CreateStackAsync(request);
        return response.StackId;
    }
}

Monitoring Stack Events

Deployments can take minutes. Here is how to poll for status updates, which is essential for CI/CD pipelines:

public async Task WaitForStackAsync(string stackName)
{
    var terminalStatuses = new HashSet<string>
    {
        "CREATE_COMPLETE", "UPDATE_COMPLETE", "DELETE_COMPLETE",
        "CREATE_FAILED", "UPDATE_FAILED", "DELETE_FAILED",
        "ROLLBACK_COMPLETE", "UPDATE_ROLLBACK_COMPLETE"
    };

    while (true)
    {
        var response = await _cfnClient.DescribeStacksAsync(
            new DescribeStacksRequest { StackName = stackName });

        var stack = response.Stacks.FirstOrDefault();
        if (stack == null)
        {
            Console.WriteLine("Stack not found or has been deleted.");
            return;
        }

        var status = stack.StackStatus.Value;
        Console.WriteLine($"[{DateTime.UtcNow:HH:mm:ss}] {stackName}: {status}");

        if (terminalStatuses.Contains(status))
        {
            if (status.Contains("FAILED") || status.Contains("ROLLBACK"))
                throw new Exception($"Stack operation failed: {status}");

            Console.WriteLine("Stack operation completed successfully.");
            return;
        }

        await Task.Delay(TimeSpan.FromSeconds(10));
    }
}

public async Task PrintRecentEventsAsync(string stackName)
{
    var response = await _cfnClient.DescribeStackEventsAsync(
        new DescribeStackEventsRequest { StackName = stackName });

    foreach (var evt in response.StackEvents.Take(10))
    {
        Console.WriteLine(
            $"{evt.Timestamp:HH:mm:ss} | {evt.LogicalResourceId,-25} | " +
            $"{evt.ResourceStatus,-25} | {evt.ResourceStatusReason}");
    }
}

Updating and Deleting Stacks

public async Task UpdateStackAsync(string stackName, string newTemplateBody)
{
    try
    {
        var request = new UpdateStackRequest
        {
            StackName = stackName,
            TemplateBody = newTemplateBody,
            Capabilities = new List<string> { "CAPABILITY_IAM" }
        };

        await _cfnClient.UpdateStackAsync(request);
        await WaitForStackAsync(stackName);
    }
    catch (AmazonCloudFormationException ex)
        when (ex.Message.Contains("No updates are to be performed"))
    {
        Console.WriteLine("No changes detected. Stack is already up to date.");
    }
}

public async Task DeleteStackAsync(string stackName)
{
    await _cfnClient.DeleteStackAsync(
        new DeleteStackRequest { StackName = stackName });

    Console.WriteLine($"Deleting stack {stackName}...");
    await WaitForStackAsync(stackName);
}

Infrastructure as Code C# — Best Practices

After deploying hundreds of stacks in production, here are the practices that matter most:

1. Always Use Tagging

Tags let you track costs, filter resources, and enforce governance. Add them at the stack level so every resource inherits them:

new MyCdkAppStack(app, "ProdStack", new StackProps
{
    Tags = new Dictionary<string, string>
    {
        ["Environment"] = "Production",
        ["Team"] = "Backend",
        ["CostCenter"] = "ENG-042"
    }
});

2. Use Stack Outputs for Cross-Stack References

// In Stack A — export a value
new CfnOutput(this, "ApiEndpoint", new CfnOutputProps
{
    Value = api.Url,
    ExportName = "ProdApiEndpoint"
});

// In Stack B — import it
var apiUrl = Fn.ImportValue("ProdApiEndpoint");

3. Set Removal Policies Intentionally

By default, stateful resources like S3 buckets and DynamoDB tables are retained when a stack is deleted. This prevents accidental data loss but can leave orphaned resources. Be explicit:

// Development — clean up everything
RemovalPolicy = RemovalPolicy.DESTROY

// Production — retain data resources
RemovalPolicy = RemovalPolicy.RETAIN

4. Unit Test Your Infrastructure

The CDK lets you test your infrastructure definitions using standard C# testing frameworks:

using Amazon.CDK;
using Amazon.CDK.Assertions;
using Xunit;

public class StackTests
{
    [Fact]
    public void Stack_Creates_DynamoDB_Table_With_Correct_Key()
    {
        var app = new App();
        var stack = new MyCdkAppStack(app, "TestStack");
        var template = Template.FromStack(stack);

        template.HasResourceProperties("AWS::DynamoDB::Table", new Dictionary<string, object>
        {
            ["KeySchema"] = new[]
            {
                new Dictionary<string, object>
                {
                    ["AttributeName"] = "OrderId",
                    ["KeyType"] = "HASH"
                }
            },
            ["BillingMode"] = "PAY_PER_REQUEST"
        });
    }

    [Fact]
    public void Stack_Creates_Encrypted_S3_Bucket()
    {
        var app = new App();
        var stack = new MyCdkAppStack(app, "TestStack");
        var template = Template.FromStack(stack);

        template.HasResourceProperties("AWS::S3::Bucket", new Dictionary<string, object>
        {
            ["VersioningConfiguration"] = new Dictionary<string, object>
            {
                ["Status"] = "Enabled"
            }
        });
    }
}

Common Pitfalls to Avoid

These mistakes cause the most deployment failures and debugging headaches:

  • Hardcoding account IDs and regions — Use Stack.Of(this).Account and Stack.Of(this).Region instead. Your code should work across environments without changes.
  • Ignoring CloudFormation limits — A single template can have at most 500 resources. Split large applications into multiple stacks.
  • Not handling "No updates to perform" — The UpdateStackAsync call throws an exception when nothing has changed. Always catch AmazonCloudFormationException with this specific message.
  • Forgetting CAPABILITY_IAM — Any stack that creates IAM roles or policies requires explicit acknowledgment. Without it, the deployment fails with an insufficient capabilities error.
  • Circular dependencies between stacks — Stack A exports a value that Stack B imports, but Stack B also exports something Stack A needs. Break the cycle with SSM Parameter Store.
  • Deploying without cdk diff first — A rename in your CDK code can cause CloudFormation to delete and recreate a resource, potentially losing data. Always review the diff.

AWS CDK vs Raw CloudFormation Templates — When to Use Each

The CDK is not a replacement for understanding CloudFormation. Here is when each approach makes sense:

  • Use AWS CDK with C# when your team is already in the .NET ecosystem, when you need reusable infrastructure components, or when you want compile-time safety. The CDK shines for complex applications with many interconnected resources.
  • Use raw CloudFormation templates when you need maximum portability (some organizations require plain YAML), when debugging CDK-generated templates, or when working with CloudFormation features that the CDK does not yet support.
  • Use the AWS SDK for .NET when you need to manage stacks from application code — deployment pipelines, multi-tenant provisioning, or admin tools that create and tear down environments on demand.

Conclusion — Deploy AWS with C# Confidently

AWS CloudFormation with C# transforms infrastructure management from a manual, error-prone process into a repeatable, testable, and version-controlled workflow. The AWS CDK gives you the expressiveness of C# for defining resources, while the AWS SDK for .NET lets you orchestrate deployments programmatically.

Key takeaways:

  • Use the AWS CDK to define infrastructure as strongly-typed C# code with compile-time validation.
  • Use the AWS SDK for .NET to create, update, monitor, and delete CloudFormation stacks from your applications.
  • Always run cdk diff before deploying and write unit tests for your infrastructure definitions.
  • Tag every stack, set removal policies intentionally, and handle the "no updates" exception in your SDK code.
  • Start small — provision a single S3 bucket or DynamoDB table, then build up to full application stacks.

Infrastructure as Code with C# is not just a convenience — it is a professional practice that reduces deployment risk and lets your team move faster. Start with the CDK example above, deploy it to a sandbox account, and iterate from there.

About csharp-coder.com
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.

Comments

Popular posts from this blog

Angular 14 CRUD Operation with Web API .Net 6.0

How to Perform CRUD Operation Using Angular 14 In this article, we will learn the angular crud (create, read, update, delete) tutorial with ASP.NET Core 6 web API. We will use the SQL Server database and responsive user interface for our Web app, we will use the Bootstrap 5. Let's start step by step. Step 1 - Create Database and Web API First we need to create Employee database in SQL Server and web API to communicate with database. so you can use my previous article CRUD operations in web API using net 6.0 to create web API step by step. As you can see, after creating all the required API and database, our API creation part is completed. Now we have to do the angular part like installing angular CLI, creating angular 14 project, command for building and running angular application...etc. Step 2 - Install Angular CLI Now we have to install angular CLI into our system. If you have already installed angular CLI into your system then skip this step.  To install angular CLI ope...

Angular 14 : 404 error during refresh page after deployment

In this article, We will learn how to solve 404 file or directory not found angular error in production.  Refresh browser angular 404 file or directory not found error You have built an Angular app and created a production build with ng build --prod You deploy it to a production server. Everything works fine until you refresh the page. The app throws The requested URL was not found on this server message (Status code 404 not found). It appears that angular routing not working on the production server when you refresh the page. The error appears on the following scenarios When you type the URL directly in the address bar. When you refresh the page The error appears on all the pages except the root page.   Reason for the requested URL was not found on this server error In a Multi-page web application, every time the application needs to display a page it has to send a request to the web server. You can do that by either typing the URL in the address bar, clicking on the Me...

Send an Email via SMTP with MailKit Using .NET 6

How to Send an Email in .NET Core This tutorial show you how to send an email in .NET 6.0 using the MailKit email client library. Install MailKit via NuGet Visual Studio Package Manager Console: Install-Package MailKit How to Send an HTML Email in .NET 6.0 This code sends a simple HTML email using the Gmail SMTP service. There are instructions further below on how to use a few other popular SMTP providers - Gmail, Hotmail, Office 365. // create email message var email = new MimeMessage(); email.From.Add(MailboxAddress.Parse("from_address@example.com")); email.To.Add(MailboxAddress.Parse("to_address@example.com")); email.Subject = "Email Subject"; email.Body = new TextPart(TextFormat.Html) { Text = "<h1>Test HTML Message Body</h1>" }; // send email using var smtp = new SmtpClient(); smtp.Connect("smtp.gmail.com", 587, SecureSocketOptions.StartTls); smtp.Authenticate("[Username]", "[Password]"); smtp.Se...