A step-by-step guide to building a RESTful API from scratch using C#, EF Core, SQL Server, Docker, and AWS ECS.
- C# and DotNet
- Docker and SQL Server
- Build Project Image and Docker Compose
- Deploy Project Image and Database to ECS
C# and DotNet
Basic install
Set up development environment
- Start Visual Studio Code.
- Install C# Dev Kit (The C# extension and the .NET Install Tool will automatically be installed)
Install dotnet sdk 8.0
Create ASP.Net Web API Project
There are two ways to create a Web API project:
Create in Visual Studio Code.
- Start Visual Studio Code.
- Command+Shift+P
- Select Create .NET Project
- Select ASP.Net Core Web API
- Select the location where you would like the new project to be created.
- Give your new project a name, like “TrilloBackend”.
- Select Create Project, the project template creates a
WeatherForecastAPI with support for Swagger.
Create with terminal
Tutorial: Create a web API with ASP.NET Core
Open terminal, change directories (
cd) to the folder that will contain the project folder. The project template creates aWeatherForecastAPI with support for Swagger.1
2
3
4dotnet new webapi --use-controllers -o TrilloBackend
cd TrilloBackend
dotnet add package Microsoft.EntityFrameworkCore.InMemory
code -r ../TrilloBackendTrust the HTTPS development certificate
1
dotnet dev-certs https --trust
Run the project, check the result with: https://localhost:PORT/swagger/index.html
- Control + F5
- write in terminal:
dotnet run - Run → Run without Debugging
Add Ef Core
There are two common ORM options: EF Core and Dapper, here is the main differences:
Open terminal, run the following commands to install EF Core and Sql Server driver.
1
2dotnet add package Microsoft.EntityFrameworkCore --version 8.0.8
dotnet add package Microsoft.EntityFrameworkCore.SqlServerCreate the model. In the project directory, create Models folder, and create Model.cs and other model files with the following code. (In this Project, I use Booking.cs, Hotel.cs, Order.cs, Review.cs, TrilloContext.cs instead of Model.cs)
- Models Code
Models/Booking.csModels/Hotel.cs1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Booking
{
public int BookingId { get; set; }
// FK
public int? HotelId { get; set; }
public int? OrderId { get; set; }
// Properties
public DateTime Date { get; set; }
public double? Price { get; set; }
public bool? isAvailable { get; set; }
// Internal
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
// Relation
public Order Order { get; set; } = new();
public Hotel Hotel { get; set; } = new();
}Models/Order.cs1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Hotel
{
public int HotelId { get; set; }
// Properties
public string? Name { get; set; }
public List<string> Gallery { get; set; } = [];
public string? Description { get; set; }
public string? Address { get; set; }
public int? TotalVote { get; set; }
// Internal
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
// Relation
public List<Review> Reviews { get; set; } = new();
public List<Booking> Bookings { get; set; } = new();
}Models/Review.cs1
2
3
4
5
6
7
8
9
10
11
12
13
14public class Order
{
public int OrderId { get; set; }
// FK
public int? UserId { get; set; }
// Properties
public double? Amount { get; set; }
public string? GuestName { get; set; }
// Internal
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
// Relation
public List<Booking> Bookings { get; set; } = new();
}Models/TrilloContext.cs1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Review
{
public int ReviewId { get; set; }
// FK
public int? UserId { get; set; }
public int? HotelId { get; set; }
// Properties
public string? Body { get; set; }
public double? Rating { get; set; }
// Internal
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
// Relation
public Hotel Hotel { get; set; } = new();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46using Microsoft.EntityFrameworkCore;
public class TrilloContext : DbContext
{
public DbSet<Hotel> Hotels { get; set; }
public DbSet<Review> Reviews { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<Booking> Bookings { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
var builder = WebApplication.CreateBuilder();
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
// Load configuration for current environment.
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", true)
.AddEnvironmentVariables()
.Build();
// Connect to the database.
options.UseSqlServer(configuration.GetConnectionString("TrilloDatabase"));
}
// Generate Date/time.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Booking>(e => {
e.Property(b => b.CreatedAt).HasDefaultValueSql("getdate()");
e.Property(b => b.UpdatedAt).HasDefaultValueSql("getdate()");
});
modelBuilder.Entity<Hotel>(e => {
e.Property(b => b.CreatedAt).HasDefaultValueSql("getdate()");
e.Property(b => b.UpdatedAt).HasDefaultValueSql("getdate()");
});
modelBuilder.Entity<Order>(e => {
e.Property(b => b.CreatedAt).HasDefaultValueSql("getdate()");
e.Property(b => b.UpdatedAt).HasDefaultValueSql("getdate()");
});
modelBuilder.Entity<Review>(e => {
e.Property(b => b.CreatedAt).HasDefaultValueSql("getdate()");
e.Property(b => b.UpdatedAt).HasDefaultValueSql("getdate()");
});
}
}
- Models Code
Add Database ConnectionStrings to appsettings.json
1
2"ConnectionStrings": {
"TrilloDatabase": "Data Source=localhost,1433\\Catalog=trillo;Database=trillo;Trusted_Connection=false;User Id=SA;password=YOUR_PASSWORD;TrustServerCertificate=True;"}- Data Source: replace ‘1433’ with your database port;
- Database: replace ‘trillo’ with your database name;
- Attention User Id need a space;
Add appsettings.Production.json for production environement.
1
2
3
4
5
6
7
8
9
10
11
12{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"TrilloDatabase": "Data Source=localhost,1433\\Catalog=trillo;Database=trillo;Trusted_Connection=false;User Id=SA;password=YOUR_PASSWORD;TrustServerCertificate=True;"
}
}If your database is up and running, you can run the following commands to create migrations and apply to database.
1
2
3
4
5
6
7
8# install tool and package
dotnet tool install --global dotnet-ef
dotnet add package Microsoft.EntityFrameworkCore.Design
# The first time, you need to execute this command to create the Migrations folder
dotnet ef migrations add InitialCreate
# this command will be replaced as migration() in the section:
dotnet ef database updateLinks:
Add Controller
Tutorial: Create a web API with ASP.NET Core
Create Controller, run the following code to create Controllers folder with file HotelsController.cs.
1
2
3
4
5
6
7
8
9dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet tool uninstall -g dotnet-aspnet-codegenerator
dotnet tool install -g dotnet-aspnet-codegenerator
dotnet tool update -g dotnet-aspnet-codegenerator
dotnet aspnet-codegenerator controller -name HotelsController -async -api -m Hotel -dc TrilloContext -outDir ControllersUpdate HotelsController.cs to add nameof in POST return, to avoid hardcode the action name in CreatedAtAction function.
Add Controller service to program.cs
1
2
3
4
5
6using Microsoft.EntityFrameworkCore;
builder.Services.AddControllers();
builder.Services.AddDbContext<TrilloContext>();
app.MapControllers();
- Code
HotelsController.csProgram.cs1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace TrilloBackend.Controllers
{
[]
[]
public class HotelsController : ControllerBase
{
private readonly TrilloContext _context;
public HotelsController(TrilloContext context)
{
_context = context;
}
// GET: api/hotels
[]
public async Task<ActionResult<IEnumerable<Hotel>>> GetHotels()
{
return await _context.Hotels
.Include(e => e.Reviews)
.Include(e => e.Bookings)
.ToListAsync();
}
// GET: api/hotels/5
[]
public async Task<ActionResult<Hotel>> GetHotel(long id)
{
var hotel = await _context.Hotels
.Include(e => e.Reviews)
.Include(e => e.Bookings)
.FirstOrDefaultAsync(e => e.HotelId == id)
;
if (hotel == null)
{
return NotFound();
}
return hotel;
}
// PUT: api/hotels/5
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[]
public async Task<IActionResult> PutHotel(int id, Hotel hotel)
{
if (id != hotel.HotelId)
{
return BadRequest();
}
_context.Entry(hotel).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!HotelExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/hotels
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[]
public async Task<ActionResult<Hotel>> PostHotel(Hotel hotel)
{
_context.Hotels.Add(hotel);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetHotel), new { id = hotel.HotelId }, hotel);
}
// PostGET: api/hotels/search
[]
public async Task<ActionResult<IEnumerable<Hotel>>> PostGetHotel(string? name, string? address)
{
// check Name and Address record
var hotelQuery = _context.Hotels
.Include(e => e.Reviews)
.Include(e => e.Bookings)
.AsQueryable();
// name && address
if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(address))
{
var result = await hotelQuery
.Where(e => EF.Functions.Like(e.Name, $"%{name}%") && EF.Functions.Like(e.Address, $"%{address}%"))
.ToListAsync();
return result;
}
// check name without address
if (!string.IsNullOrEmpty(name))
{
var result = await hotelQuery
.Where(e => EF.Functions.Like(e.Name, $"%{name}%"))
.ToListAsync();
if (result.Any())
return result;
}
// chenck address without name
if (!string.IsNullOrEmpty(address))
{
var result = await hotelQuery
.Where(e => EF.Functions.Like(e.Address, $"%{address}%"))
.ToListAsync();
if (result.Any())
return result;
}
// return []
return new List<Hotel>();
}
// DELETE: api/hotels/5
[]
public async Task<IActionResult> DeleteHotel(int id)
{
var hotel = await _context.Hotels.FindAsync(id);
if (hotel == null)
{
return NotFound();
}
_context.Hotels.Remove(hotel);
await _context.SaveChangesAsync();
return NoContent();
}
private bool HotelExists(int id)
{
return _context.Hotels.Any(e => e.HotelId == id);
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49using System.Text.Json.Serialization;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Fixing the error “A possible object cycle was detected”.
builder.Services.AddControllers().AddJsonOptions(x =>
x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
builder.Services.AddDbContext<TrilloContext>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Prepare database.
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<TrilloContext>();
if (app.Environment.IsDevelopment())
{
Console.WriteLine($"DbConnectionString: {context.Database.GetConnectionString()}");
}
// Create Database and apply migrations.
context.Database.Migrate();
// Seed data to tables.
DbInitializer.Initialize(context);
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// Test api.
app.MapGet("/Ping", () =>
{
return "Pong";
})
.WithName("Ping")
.WithOpenApi();
app.UseHttpsRedirection();
app.MapControllers();
app.Run();
Automatically Initialise Database
How and where to call Database.EnsureCreated and Database.Migrate?
- Change program.cs, add
Migrate()to create database, apply migration and seed data.
- Add code that populates the database with data. Create
Data/DbInitializer.cswith the following code:- Data/DbInitializer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59public static class DbInitializer {
public static void Initialize(TrilloContext context) {
// Check if any data exist.
if (context.Hotels.Any()) {
return;
}
var hotels = new Hotel[] {
new Hotel{Name="The Venetian Macao",Gallery=["https://image-tc.galaxy.tf/wijpeg-9vualzt3dbue0hi00ba4q49ub/chatwalhotelnyc-c-004-build-crop.jpg?width=1920"],Description="This is a Hotel located in Macao",Address="Marco China",TotalVote=99, Bookings=[]},
new Hotel{Name="The Parisian Macao",Gallery=["https://dynamic-media-cdn.tripadvisor.com/media/photo-s/01/ea/d8/2d/grand-canal-shoppes.jpg?w=600&h=-1&s=1"],Description="This is a Hotel located in China",Address="China",TotalVote=96},
new Hotel{Name="Nina Hotel Tsuen Wan West",Gallery=["https://dynamic-media-cdn.tripadvisor.com/media/daodao/photo-s/0c/7b/d9/f2/exterior-night.jpg?w=600&h=-1&s=1"],Description="This is a Hotel located in Auckland",Address="Auckland",TotalVote=79},
new Hotel{Name="Nina Hotel Causeway Bay",Gallery=["https://cf.bstatic.com/xdata/images/hotel/max1024x768/118479281.jpg?k=d5090d90ae7919b4637f2d7d08d0ae0df7517e4185eaebed5a5907e53cd3801d&o=&hp=1"],Description="This is a Hotel located in NZ",Address="NZ",TotalVote=39},
new Hotel{Name="Conrad Macao",Gallery=["test1"],Description="This is a Hotel located in Beijing",Address="Beijing",TotalVote=27},
new Hotel{Name="Legend Palace Hotel",Gallery=["test2"],Description="This is a Hotel located in Shanghai",Address="Shanghai",TotalVote=30},
new Hotel{Name="Royal Plaza Hotel",Gallery=["test3"],Description="This is a Hotel located in Shenzhen",Address="Shenzhen",TotalVote=15},
};
context.Hotels.AddRange(hotels);
context.SaveChanges();
var reviews = new Review[] {
new Review{UserId=101,HotelId=hotels[0].HotelId,Body="The environment is very nice.",Rating=8.8},
new Review{UserId=102,HotelId=hotels[1].HotelId,Body="The food is very nice.",Rating=7.8},
new Review{UserId=103,HotelId=hotels[2].HotelId,Body="The price is very nice.",Rating=6.8},
new Review{UserId=104,HotelId=hotels[3].HotelId,Body="The bed is very nice.",Rating=3.8},
new Review{UserId=105,HotelId=hotels[4].HotelId,Body="The location is very nice.",Rating=8.5},
new Review{UserId=106,HotelId=hotels[5].HotelId,Body="The transport is very nice.",Rating=1.8},
new Review{UserId=107,HotelId=hotels[6].HotelId,Body="The people is very nice.",Rating=8.2},
};
context.Reviews.AddRange(reviews);
context.SaveChanges();
var orders = new Order[] {
new Order{UserId=101,Amount=200.3,GuestName="Ella"},
new Order{UserId=102,Amount=229,GuestName="Hank"},
new Order{UserId=103,Amount=93.4,GuestName="Mark"},
new Order{UserId=104,Amount=3784.4,GuestName="Tala"},
new Order{UserId=105,Amount=278743.4,GuestName="Ada"},
new Order{UserId=106,Amount=3985,GuestName="Hui"},
new Order{UserId=107,Amount=3875.6,GuestName="Kswi"},
new Order{UserId=108,Amount=438273.5,GuestName="Edsa"},
};
context.Orders.AddRange(orders);
context.SaveChanges();
var bookings = new Booking[] {
new Booking{HotelId=hotels[0].HotelId,OrderId=orders[0].OrderId,Price=83.3,isAvailable=true},
new Booking{HotelId=hotels[1].HotelId,OrderId=orders[1].OrderId,Price=384.5,isAvailable=false},
new Booking{HotelId=hotels[2].HotelId,OrderId=orders[2].OrderId,Price=22.3,isAvailable=true},
new Booking{HotelId=hotels[3].HotelId,OrderId=orders[3].OrderId,Price=55.6,isAvailable=true},
new Booking{HotelId=hotels[4].HotelId,OrderId=orders[4].OrderId,Price=394.5,isAvailable=false},
new Booking{HotelId=hotels[5].HotelId,OrderId=orders[0].OrderId,Price=78,isAvailable=false},
new Booking{HotelId=hotels[6].HotelId,OrderId=orders[6].OrderId,Price=92.4,isAvailable=true},
new Booking{HotelId=hotels[2].HotelId,OrderId=orders[2].OrderId,Price=100,isAvailable=false},
};
context.Bookings.AddRange(bookings);
context.SaveChanges();
}
}
- Data/DbInitializer.cs
Add .gitignore for C#
1 |
|
Troubleshooting & Tips
Tips
- Each time the database or tables are changed, migrations and updates must be executed
- Generated Datetime in table: Generate Values - EF Core
Add different table join
Add SQL
LIKEoperation: EF.Functions.LikeEntity framework EF.Functions.Like vs string.Contains
Troubleshooting
Error: A connection was successfully established with the server, but then an error occurred during the pre-login handshake. ‣
Fix: Add
TrustServerCertificate=Trueto ConnectionStringsError: “A possible object cycle was detected” in different versions of ASP.NET Core
Fixing the error “A possible object cycle was detected” in different versions of ASP.NET Core
Foreign-Key
- Link: How can I retrieve Id of inserted entity using Entity framework?
context.Reviews.AddRange(reviews)will automatically generate the primary key, which can be called directly later. The error occurs due to a conflict; a foreign key needs to reference the primary key for it to work.
We have already completed all the code, now we can start deploying our project!
Docker and SQL Server
Basic Install
- install Docker: https://www.docker.com/
- install image of sqlserver, open terminal, execute this command any path:
1
docker pull [mcr.microsoft.com/mssql/server](http://mcr.microsoft.com/mssql/server)
- install Beekeeper: https://www.beekeeperstudio.io/get
Run SQL Server
run sqlserver in docker. open terminal, run following command: https://hub.docker.com/r/microsoft/mssql-server
1 | docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=YOUR_PASSWORD" -e "MSSQL_PID=Evaluation" -p 1433:1433 --name sqlpreview --hostname sqlpreview -d mcr.microsoft.com/mssql/server |
- Default User Name is ‘SA’
- Set database password in MSSQL_SA_PASSWORD
- set database port: 1433
Beekeeper connect database
- make sure database is running!
- create new connection, choose SQL Server
- input User and Password, check ‘Trust Server Certificate’, click Connect.
- Correct connection:
- Create database
Build Project Image and Docker Compose
Add Dockerfile and .dockerignore to Project
Run an ASP.NET Core app in Docker containers
Dockerfile: It uses dotnet publish the same way you will do in this section to build and deploy.
1 | FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build |
.dockerignore
1 | # directories |
Build project image
navigate to the project folder, run the dotnet publish command: (I have written this command in Dockerfile, we can ignore this step)
1
dotnet publish -c Release -o published
Build Project image
Attention there is a point in the end! I advice to name image as dockercount/imagename:date.version so that you can easy to manage different versions.
1
docker build -t ella0110/trillobackend:20241005.1 .
Run image. If your image doesn’t need database, then you can run it directly. If you need database, you should use docker compose which is in the next section.
1
docker run -it --rm -p 8080:8080 ella0110/trillobackend:20241005.1
Docker Compose
Install docker compose, open terminal, run these command:
1
2brew search docker compose
brew install docker-composeDefine services in a Compose file
Docker Compose simplifies the control of your entire application stack, making it easy to manage services, networks, and volumes in a single, comprehensible YAML configuration file.
Create a file called
compose.yamlin your project directory and paste the following:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40services:
web:
image: "ella0110/trillobackend:20241005.3"
ports:
- "8080:8080"
environment:
- ASPNETCORE_ENVIRONMENT=Production
depends_on:
sqlserver:
condition: service_healthy
sqlserver:
image: "mcr.microsoft.com/mssql/server:latest"
environment:
- ACCEPT_EULA=Y
- MSSQL_SA_PASSWORD=@Aa12345678
ports:
- "1433:1433"
healthcheck:
test:
[
"CMD",
"/opt/mssql-tools18/bin/sqlcmd",
"-C",
"-S",
"localhost",
"-U",
"sa",
"-P",
"@Aa12345678",
"-Q",
"SELECT 1",
"-b",
"-o",
"/dev/null",
]
# test: /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P ${MSSQL_SA_PASSWORD} -Q 'SELECT 1' -b -o /dev/null
interval: 10s
timeout: 3s
retries: 10
start_period: 10sRun docker compose
1
docker compose up
Upload Image to Docker Hub
- open terminal, login to docker and run this command to push image to your docker hub account.
1 | docker push ella0110/trillobackend |
- if you want to change your image name, use this command:
1 | docker tag trillo-backend ella0110/trillobackend |
Troubleshooting & Tips
Tips
Check the health_check log
trillobackend-sqlserver-1is container name1
docker inspect --format "{{json .State.Health}}" trillobackend-sqlserver-1
Troubleshooting
Dependency issue: On first run, the database may not be ready when back end tries to initialise the database, and cause back end service failing to start.
- Link: Stack Overflow
- Fix: Add health check and depend on command
When check health, it’s always failed
- should be
/opt/mssql-tools18/bin/sqlcmdrather than/opt/mssql-tools/bin/sqlcmd, can test it in Docker/Containers/sqlserver/Exec
- should be
Error:
[Microsoft][ODBC Driver 18 for SQL Server]SSL Provider: [error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed:self signed certificate]- Link: Stack Overflow
- Fix: Add
-Cto health check
Deploy Project Image and Database to ECS
Create Task definitions
login your AWS account
search ECS, choose Elastic Container Service
choose Task definitions, click Create new task definition/ Create new task definition
input Task definition family name
you can change Task size if you want
Container-1, you can input infomation about your database
Name: sqlserver;
Image URI: mcr.microsoft.com/mssql/server:latest
Environment varaiables: follow with the section: Run SQL Server
<img width="380" alt="envvariable" src="https://github.com/user-attachments/assets/2deba519-07d4-4e6c-8f3c-ba0fc59c9a8e">HealthCheck: CMD-SHELL,/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P ‘your password’ -Q ‘SELECT 1’ -b -o /dev/null
Interval: 10
Timeout: 3
Start period: 10
Retries: 10
Container-2, you can input information about your project image
Name: your project
Image URI: eg. ella0110/trillobackend:20241005.4
Port mappings
<img width="765" alt="port" src="https://github.com/user-attachments/assets/3a5bc4dc-7a01-4413-9de9-351de303ea7b">Startup dependency ordering
<img width="755" alt="depend" src="https://github.com/user-attachments/assets/7e5daeb9-5784-4673-8a6e-51fab3cbc76f">
click Create
Create Clusters
Clusters → Create cluster: input Cluster name, click Create
Deploy your task to cluster
- Choose your task, click Deploy→ Create Service
- Choose your Cluster, then click Create
Troubleshooting & Tips
Tips
The public IP generated by ECS is inaccessible because a specific IP was used, not open IP range.
Fix: VPC → Security Groups → click Security group ID → Inbound rules → Edit inbound rules → add a new rule
Troubleshooting
Error:
Unhandled **exception**. Microsoft.Data.SqlClient.SqlException (0x80131904): A network-related or instance-specific **error** occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, **error**: 35 - An internal **exception** was caught)- Link: Stack Overflow
- Fix:
Healthcheck can not work
- Link: AWS - HealthCheck
1
CMD-SHELL,/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P '@Aa12345678' -Q 'SELECT 1' -b -o /dev/null