Skip to content
ExponaBLOG
AWS FOR DEVELOPERS · DAY 068 / 100

Deploying .NET on AWS: a practical guide for developers

Options from simple to serverless.

Expona Admin7 min read

Deploying .NET on AWS: a practical guide for developers

You've built a solid .NET application and now you need to get it running on AWS—but the sheer number of options can feel overwhelming. Whether you're running a containerized API that scales with demand, a lightweight function handling sporadic requests, or a traditional application that needs full control, AWS has a path for you. This guide walks you through four proven deployment approaches, each suited to different workloads and team preferences.

Why deployment strategy matters

Choosing the right deployment method affects your team's operational burden, your infrastructure costs, and how quickly you can scale when traffic spikes. A startup team with limited DevOps experience benefits from managed services that handle orchestration automatically. A mature team running mission-critical systems may prefer the control and observability of EC2 instances. Understanding the trade-offs lets you make decisions based on your actual constraints, not just what feels familiar.

Elastic Beanstalk — Quickest start, less control

Elastic Beanstalk is AWS's managed platform-as-a-service offering. You give it your .NET application code (or a Docker image), and it handles provisioning EC2 instances, load balancing, scaling policies, and health checks. The appeal is obvious: minimal infrastructure thinking required.

When to use it: You're deploying a monolithic ASP.NET Core web application and want to get to production fast without managing security groups, Auto Scaling Groups, or deployment automation. Beanstalk shines for straightforward web apps where you don't need exotic customization.

A practical example: You have a legacy ASP.NET Framework application that runs on Windows and connects to an on-premises SQL Server via VPN. You package it as a .zip file, upload it to Beanstalk, and within minutes you have a load-balanced, auto-scaled deployment. Your team focuses on code; Beanstalk handles the rest.

Trade-offs: You lose granular control over networking, instance types, and deployment timing. If you need to SSH into instances for debugging or run custom initialization scripts reliably, you'll find Beanstalk's abstractions frustrating. Pricing can also creep up because Beanstalk often over-provisions to ensure availability.

Getting started:

bash

# Install the Elastic Beanstalk CLI
pip install awsebcli

# Initialize your application
eb init -p dotnet-6.0 my-app

# Create and deploy to an environment
eb create production --instance-type t3.medium
eb deploy

Beanstalk works best when you're comfortable with its opinionated approach and don't need deep infrastructure customization. For many teams, it's the right first step.

ECS Fargate — Containerized APIs with autoscaling. My default

ECS Fargate lets you run Docker containers without managing the underlying EC2 instances. You define a task (a containerized .NET application), set CPU and memory requirements, and Fargate handles scheduling and scaling. It's the sweet spot for modern API development: containerization plus managed infrastructure.

Why we reach for Fargate: It combines the reproducibility of containers with AWS's automation. You define everything in code (Terraform, CDK, or CloudFormation), version your infrastructure, and deploy consistently. Networking is explicit and transparent. You can integrate with load balancers, auto-scaling policies, and service discovery without fighting a platform's opinions.

Real-world scenario: Your team maintains a collection of microservices—order processing, inventory updates, payment handling. Each runs in its own container on Fargate, scaled independently based on CPU or request count. One service handles bursty traffic (batch processing jobs); another is steady-state. Fargate lets you tune each without compromise.

Example task definition (JSON):

bash

{
  "family": "my-dotnet-api",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "containerDefinitions": [
    {
      "name": "api",
      "image": "123456789.dkr.ecr.us-east-1.amazonaws.com/my-app:v1.2.3",
      "portMappings": [
        {
          "containerPort": 5000,
          "hostPort": 5000,
          "protocol": "tcp"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/my-app",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "environment": [
        {
          "name": "ASPNETCORE_ENVIRONMENT",
          "value": "Production"
        }
      ]
    }
  ]
}

Setting up autoscaling: Fargate integrates with Application Auto Scaling. You define target tracking policies—for example, keep CPU at 70 percent. When traffic increases, Fargate spins up new tasks automatically. When it drops, tasks are terminated. No standing capacity sitting idle.

Networking and load balancing: Fargate tasks run in your VPC. You attach an Application Load Balancer (ALB) to route traffic, set up health checks, and configure sticky sessions if needed. Security groups control ingress and egress explicitly. It's transparent infrastructure.

Cost considerations: You pay for CPU and memory allocated to tasks, per second, plus data transfer and load balancer charges. For sustained workloads, EC2 can be cheaper; for variable traffic, Fargate's pay-per-task model often wins.

Lambda — Minimal APIs on Lambda for spiky traffic

AWS Lambda lets you deploy .NET code as serverless functions, scaling from zero to thousands of concurrent executions automatically. You pay only for compute time consumed, rounded to the nearest millisecond. For workloads with unpredictable or sparse traffic, Lambda is compelling.

When to reach for Lambda: You're building an API that processes webhooks intermittently, generates reports on a schedule, resizes images after upload, or handles occasional backend jobs. Sitting idle doesn't cost money. Cold starts matter less when traffic is infrequent.

Building a minimal ASP.NET Core API on Lambda: You use the AWS Lambda tooling for .NET, which wraps your ASP.NET Core application and exposes it via API Gateway. Requests route through the gateway to your Lambda function, which starts (or reuses a warm container), processes the request, and returns a response.

Example handler:

bash

using Amazon.Lambda.AspNetCoreServer;
using Microsoft.AspNetCore.Hosting;

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

public class LambdaEntryPoint : APIGatewayProxyFunction
{
    protected override void Init(IWebHostBuilder builder)
    {
        builder
            .UseStartup<Startup>();
    }
}

Deploy this to Lambda with a handler of MyProject::MyProject.LambdaEntryPoint::FunctionHandlerAsync, wire it to API Gateway, and you're live. No servers to manage, no scaling knobs to turn.

Trade-offs: Cold starts introduce latency (typically 1–3 seconds for .NET) on first invocation after idle time. This matters for interactive APIs but is acceptable for background jobs. Lambda's execution environment is ephemeral; you can't rely on local disk state or long-running background tasks. Maximum execution time is 15 minutes. Debugging is harder than on EC2 or ECS because you can't SSH into the environment.

When Lambda doesn't fit: If your workload is steady-state and runs continuously, EC2 or Fargate is cheaper and simpler. If you need sub-second response times consistently, cold starts are painful. If your application holds persistent connections (e.g., WebSocket, message queue consumer), Lambda isn't designed for it.

EC2 — Full control for legacy or special workloads

Sometimes you need full control: Windows Server for legacy .NET Framework code, custom networking, GPUs for ML workloads, or specific compliance requirements. EC2 instances give you that control but require you to manage patching, scaling, and monitoring yourself.

When EC2 makes sense: You're running a .NET Framework 4.8 application that can't be containerized easily. You need persistent disk storage for large files. You're running a Windows-only third-party tool. You need to open unusual firewall rules or integrate with on-premises infrastructure in ways Fargate doesn't support.

Real scenario: Your company acquired a legacy WinForms service that runs batch jobs overnight, reading from shared storage and writing to a custom database. Migrating it to .NET Core would take months. EC2 with a Windows Server AMI lets you run it as-is, with Auto Scaling to handle the nightly load spike.

Setting up EC2 for .NET: Launch an instance from a Windows Server AMI, RDP in, install .NET runtime and your application, install IIS, configure a Windows service to start your app on boot, and attach to an Auto Scaling Group and load balancer. Use CloudWatch for monitoring, Systems Manager for patching, and CloudFormation or Terraform to codify the setup.

Cost and operational burden: EC2 is usually cheaper than Fargate or Lambda for continuous, high-throughput workloads, but you're on the hook for OS updates, security patches, and instance maintenance. An Auto Scaling Group minimizes manual intervention, but it's still more operational overhead than Fargate or Lambda.

Common mistakes

  • Choosing Beanstalk because it's "easy," then fighting its constraints. Easy initial deployment isn't the same as easy operations. Evaluate whether Beanstalk's trade-offs match your actual requirements before committing.
  • Underestimating Lambda cold starts. If you deploy a Minimal API to Lambda without considering cold start latency, your monitoring will show p99 response times that aren't representative. Test cold starts in your staging environment. Consider provisioned concurrency if response time is critical.
  • Running stateful workloads on Fargate without external state. Fargate tasks are ephemeral. If your application caches data in memory or writes to local disk, you'll lose it when the task stops. Use external caches (ElastiCache) and databases (RDS) instead.
  • Not containerizing before deploying to Fargate. Fargate requires containers. If you're not familiar with Docker, pushing that learning onto production deployment day is a mistake. Build and test your container locally first.
  • Ignoring security group and IAM policy configuration. Regardless of deployment method, undershooting security configuration is risky. Each service needs explicit, minimal permissions. Default security groups and overly permissive IAM roles invite compromise.

Key takeaways

  • Elastic Beanstalk wins for quick web app deployment with minimal infrastructure thinking, at the cost of control.
  • Fargate is the default for most teams: containerized, scalable, transparent infrastructure with reasonable operational simplicity.
  • Lambda excels for spiky or sparse workloads where you pay only for actual usage; cold starts and the 15-minute timeout are the main constraints.
  • EC2 remains necessary for legacy .NET Framework applications, Windows-only requirements, or when continuous, high-throughput workloads make per-second pricing uneconomical.
  • Start by defining your workload pattern (steady, spiky, batch, continuous), expected concurrency, and team operational capacity. Deployment method flows from that analysis.
  • Containerization (Docker) is increasingly table stakes; mastering it simplifies both local development and AWS deployment across Fargate, ECS on EC2, and Lambda.
  • Test your chosen deployment method's critical path—scaling, failover, deployment—before production. Surprises in production are expensive.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles