diff --git a/nginx-aspnet-mysql/README.md b/nginx-aspnet-mysql/README.md new file mode 100644 index 0000000..ea7fcab --- /dev/null +++ b/nginx-aspnet-mysql/README.md @@ -0,0 +1,68 @@ +## Compose sample application +### ASP.NET server with an Nginx proxy and a MySQL database + +Project structure: +``` +. +├── backend +│   ├── Dockerfile +│   ├── aspnet.csproj +│   └── Program.cs +├── db +│   └── password.txt +├── docker-compose.yaml +├── proxy +│   ├── conf +│   └── Dockerfile +└── README.md +``` + +[_docker-compose.yaml_](docker-compose.yaml) +``` +services: + backend: + build: backend + ... + db: + image: mysql:8.0.19 + ... + proxy: + build: proxy + ports: + - 80:80 + ... +``` +The compose file defines an application with three services `proxy`, `backend` and `db`. +When deploying the application, docker-compose maps port 80 of the proxy service container to port 80 of the host as specified in the file. +Make sure port 80 on the host is not already being in use. + +## Deploy with docker-compose + +``` +$ docker-compose up -d +``` + +## Expected result + +Listing containers must show three containers running and the port mapping as below: +``` +$ docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +8906b14c5ad1 nginx-aspnet-mysql_proxy "nginx -g 'daemon of…" 2 minutes ago Up 2 minutes 0.0.0.0:80->80/tcp nginx-aspnet-mysql +l_proxy_1 +13e0e0a7715a nginx-aspnet-mysql_backend "/server" 2 minutes ago Up 2 minutes 8000/tcp nginx-aspnet-mysq +l_backend_1 +ca8c5975d205 mysql:5.7 "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 3306/tcp, 33060/tcp nginx-aspnet-mysql +l_db_1 +``` + +After the application starts, navigate to `http://localhost:80` in your web browser or run: +``` +$ curl localhost:80 +["Blog post #0","Blog post #1","Blog post #2","Blog post #3","Blog post #4"] +``` + +Stop and remove the containers +``` +$ docker-compose down +``` diff --git a/nginx-aspnet-mysql/backend/Dockerfile b/nginx-aspnet-mysql/backend/Dockerfile new file mode 100755 index 0000000..dd5d671 --- /dev/null +++ b/nginx-aspnet-mysql/backend/Dockerfile @@ -0,0 +1,17 @@ + +FROM mcr.microsoft.com/dotnet/aspnet:5.0 as base +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build +COPY . /src +WORKDIR /src +RUN ls +RUN dotnet build "aspnetapp.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "aspnetapp.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "aspnetapp.dll"] \ No newline at end of file diff --git a/nginx-aspnet-mysql/backend/Program.cs b/nginx-aspnet-mysql/backend/Program.cs new file mode 100644 index 0000000..535d9b8 --- /dev/null +++ b/nginx-aspnet-mysql/backend/Program.cs @@ -0,0 +1,84 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Primitives; +using MySql.Data; +using MySql.Data.MySqlClient; + +class Program +{ + public static void Main(string[] args) => WebHost.CreateDefaultBuilder(args) + .Configure(async app => + { + app.UseRouting(); + + string password = File.ReadAllText("/run/secrets/db-password"); + string connectionString = $"server=db;user=root;database=example;port=3306;password={password}"; + + app.UseEndpoints(e => + { + e.MapGet("/", context => { + using MySqlConnection connection = new MySqlConnection(connectionString); + var titles = new List(); + + try + { + Console.WriteLine("Connecting to MySQL..."); + connection.Open(); + + string sql = "SELECT title FROM blog"; + using var cmd = new MySqlCommand(sql, connection); + using MySqlDataReader reader = cmd.ExecuteReader(); + + while (reader.Read()) + { + titles.Add(reader.GetString(0)); + } + reader.Close(); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + context.Response.StatusCode = 500; + return Task.CompletedTask; + } + connection.Close(); + + context.Response.StatusCode = 200; + context.Response.WriteAsJsonAsync(titles); + + return Task.CompletedTask; + }); + }); + Prepare(connectionString); + + }).Build().Run(); + + private static void Prepare(string connectionString) + { + using MySqlConnection connection = new MySqlConnection(connectionString); + + connection.Open(); + using var transation = connection.BeginTransaction(); + + using MySqlCommand cmd1 = new MySqlCommand("DROP TABLE IF EXISTS blog", connection, transation); + cmd1.ExecuteNonQuery(); + + using MySqlCommand cmd2 = new MySqlCommand("CREATE TABLE IF NOT EXISTS blog (id int NOT NULL AUTO_INCREMENT, title varchar(255), PRIMARY KEY (id))", connection, transation); + cmd2.ExecuteNonQuery(); + + for (int i = 0; i < 5; i++) + { + using MySqlCommand insertCommand = new MySqlCommand( $"INSERT INTO blog (title) VALUES ('Blog post #{i}');", connection, transation); + insertCommand.ExecuteNonQuery(); + } + transation.Commit(); + connection.Close(); + } +} \ No newline at end of file diff --git a/nginx-aspnet-mysql/backend/aspnetapp.csproj b/nginx-aspnet-mysql/backend/aspnetapp.csproj new file mode 100644 index 0000000..25b2321 --- /dev/null +++ b/nginx-aspnet-mysql/backend/aspnetapp.csproj @@ -0,0 +1,8 @@ + + + net5.0 + + + + + \ No newline at end of file diff --git a/nginx-aspnet-mysql/db/password.txt b/nginx-aspnet-mysql/db/password.txt new file mode 100644 index 0000000..cea6d05 --- /dev/null +++ b/nginx-aspnet-mysql/db/password.txt @@ -0,0 +1 @@ +db-q5n2g \ No newline at end of file diff --git a/nginx-aspnet-mysql/docker-compose.yaml b/nginx-aspnet-mysql/docker-compose.yaml new file mode 100644 index 0000000..4190b59 --- /dev/null +++ b/nginx-aspnet-mysql/docker-compose.yaml @@ -0,0 +1,41 @@ +version: "3.7" +services: + backend: + build: backend + secrets: + - db-password + depends_on: + - db + environment: + - ASPNETCORE_URLS=http://+:8000 + depends_on: + db: + condition: service_healthy + db: + image: mysql:8.0.19 + command: '--default-authentication-plugin=mysql_native_password' + restart: always + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "--silent"] + interval: 3s + retries: 5 + start_period: 30s + secrets: + - db-password + volumes: + - db-data:/var/lib/mysql + environment: + - MYSQL_DATABASE=example + - MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db-password + + proxy: + build: proxy + ports: + - 80:80 + depends_on: + - backend +volumes: + db-data: +secrets: + db-password: + file: db/password.txt \ No newline at end of file diff --git a/nginx-aspnet-mysql/proxy/Dockerfile b/nginx-aspnet-mysql/proxy/Dockerfile new file mode 100755 index 0000000..f5760d0 --- /dev/null +++ b/nginx-aspnet-mysql/proxy/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx:1.13-alpine +COPY conf /etc/nginx/conf.d/default.conf \ No newline at end of file diff --git a/nginx-aspnet-mysql/proxy/conf b/nginx-aspnet-mysql/proxy/conf new file mode 100755 index 0000000..f6d2195 --- /dev/null +++ b/nginx-aspnet-mysql/proxy/conf @@ -0,0 +1,8 @@ +server { + listen 80; + server_name localhost; + location / { + proxy_pass http://backend:8000; + } + +}