I’m in the middle of launching my latest project Sheldon, a live chat service which features embedded live chatting on any website with Ai assistant integration. This project I’ve tried to use the most modern version of .NET available, .NET 9. I’ve also chosen to host my app on AWS using Linux instances and ARM based t4g on top of Elastic Beanstalk.
Elastic Beanstalk is not a perfect system, in fact it’s pretty temperamental to use, however, I am most familiar with it so that’s the route I’m going. In the end deploying this setup is actually pretty easy, there are only few gotcha things to adjust.
Why use the t4g and ARM?
- ARM processors use significantly less energy which translate into much lower cost.
- t* instances feature burstable processing. If your app uses relatively consistent resources and only occasionally spikes then burstable instances may make sense.
- Be mindful When choosing t4g they are not always available in every availability zone. If you create your environment and receive an error, you may need to exclude certain zones in order for it to allow you to configure the environment.
Use AWS Toolkit for Deployments
Download the latest AWS Toolkit for quickly configuring your environment. You’ll need to setup the toolkit and verify it’s connected to AWS by creating and entering your credentials.
- Right click your project in Visual Studio, choose the option to Deploy to AWS.
- Chose the option to deploy to Linux and AWS Elastic Beanstalk.
- Configure your EB environment to use the t4g instance.
- Important - In order to deploy .NET onto Linux ARM you must pass the command line argument
-r linux-x64
during deployment. At the bottom of the environment options add-r linux-x64
to the .NET build options. - If everything goes well you should see your app after deployment on the temporary URL.
Inspect Logs for Issues
I would highly recommend you inspect your logs next. In Beanstalk download the last 100 lines at least just to verify things are working normally. You’ll likely see a lot of lines like this:
2024/11/22 21:42:12 [warn] 11044#11044: *1163 an upstream response is buffered to a temporary file /var/lib/nginx/tmp/proxy/2/05/0000000052 while reading upstream, client: 24.209.20.179, server: , request: "GET /assets/css/plugins/bootstrap.min.css HTTP/1.1", upstream: "http://127.0.0.1:5000/assets/css/plugins/bootstrap.min.css"
The messages you’re seeing in the Nginx logs are warnings about upstream responses being buffered to temporary files. Here’s a breakdown of what this means and how you can address it:
What This Means
Upstream Buffering:
- Nginx is acting as a reverse proxy for your application (running on
http://127.0.0.1:5000
). - When it receives a large response from the upstream server (your app), Nginx buffers the response.
- If the response size exceeds the configured in-memory buffer limits, Nginx writes the response to a temporary file on disk (in
/var/lib/nginx/tmp/proxy/
).
- Nginx is acting as a reverse proxy for your application (running on
Why This Happens:
- The response from your application exceeds the buffer size configured for Nginx.
- Static files (like CSS, images, or fonts) or large responses from your app are triggering this behavior.
Impact:
- This behavior isn’t necessarily a critical issue but can lead to:
- Increased disk I/O.
- Performance degradation if too many requests are buffered to disk simultaneously.
- This behavior isn’t necessarily a critical issue but can lead to:
Adding a user config for nginx
In EB you must create a specific folder structure and a .conf
file that EB will use to supplement or override the nginx configuration.
Create the following folder structure at the root of your project .platform\nginx\nginx.conf
Be Sure to right click the file > Copy to output directory set to always
Be sure this file is UTF-8 Encoded
These are the changes to prevent the error, but I chose to overwrite the whole file.
proxy_buffer_size 16k;
proxy_buffers 4 16k;
proxy_busy_buffers_size 32k;
client_max_body_size 40M; # Adjust if large uploads are expected
The complete nginx.conf
worker_processes auto;
worker_rlimit_nofile 200000;
events {
worker_connections 4096;
}
http {
server_tokens off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
include conf.d/*.conf;
map $http_upgrade $connection_upgrade {
default "upgrade";
}
server {
listen 80 default_server;
access_log /var/log/nginx/access.log main;
proxy_buffer_size 16k;
proxy_buffers 4 16k;
proxy_busy_buffers_size 32k;
client_max_body_size 40M; # Adjust if large uploads are expected
client_header_timeout 60;
client_body_timeout 60;
keepalive_timeout 60;
gzip off;
gzip_comp_level 4;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
# Include the Elastic Beanstalk generated locations
include conf.d/elasticbeanstalk/*.conf;
# Configure the SignalR Endpoint
location /chatHub {
# App server url
proxy_pass http://localhost:5000;
# Configuration for WebSockets
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_cache off;
# WebSockets were implemented after http/1.0
proxy_http_version 1.1;
# Configuration for ServerSentEvents
proxy_buffering off;
# Configuration for LongPolling or if your KeepAliveInterval is longer than 60 seconds
proxy_read_timeout 100s;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
You may need to tweak the values further depending on the size of files your site serves.
You can now redeploy your site and EB will apply this configuration to your nginx server. There are various other options available as well including .ebextensions
and overriding the full nginx configuration if needed.
AWS Toolkit Issues
I originally wrote this post successfully deploying my project using the built in Publish to AWS
dialog. Unfortunately I discovered a severe bug with the process. Sadly this isn’t the first bug I’ve found and reported with AWS toolkit for visual studio.
When I originally setup my project to deploy I noticed I could not see it in the deploy dialog as a target. I shrugged it off and used the built in dialog to deploy my project while creating a new environment. The big issues arose when I wanted to change my environment outside of Visual Studio. The Toolkit only offers a fraction of the configuration options that are found on AWS dashboard. Once I redeployed my application it immediately overwrote all the of the configuration changes I made. :(
I have an open issue with AWS on github regarding this, so hopefully they will fix it. As far as I can tell this is a bug as the legacy dialog does not do this.
Using the Legacy Deploy
I’ve used the legacy deploy dialog for years with .net 4.8. It’s predictable but lacks several crucial options. Unlike the new gui, this will not overwrite your environment config. Notably you cannot set the command line args to specify -r linux-x64
in the gui. The way to accomplish this is as follows:
- Create a
aws-tools-beanstalk-defaults.json
file in the root of your project. - Here is an example of the contents, the crucial part which took a while to find is that “publish-options” allows you to pass this into the GUI or CLI when deploying. Fumbling around in the
dotnet eb -help
I finally found a list of the additional parameters.
{
"region": "us-east-1",
"configuration": "Release",
"framework": "net9.0",
"self-contained": true,
"application": "SheldonWeb2",
"environment": "SheldonWeb2-env",
"enable-xray": false,
"enhanced-health-type": "enhanced",
"additional-options": "",
"proxy-server": "nginx",
"publish-options": "-r linux-arm64"
}
- If you plan to continually use the GUI legacy deployment dialog you’ll need to make sure you don’t overwrite this config every time you deploy. There is a checkbox at the last step you’ll need to uncheck.
- Optionally you can also use the
dotnet eb
deployment flow as well, which also reads this config file.