utilizing the basic weather forecast asp.net 8 basic starter project, i am trying to setup a nginx reverse proxy utilizing docker-compose in an azure app service. I am utilizing 2 services in the docker container, 1 for nginx (loadbalancertest2) and 1 for asp.net core 8 (api)
I have all the code files setup, two different docker files for the 2 services, a wait for it, that attempts to access the api before starting nginx
I can get both services to respond independently locally , but i cannot get the reverse proxy to direct traffic to the api endpoint.
locally:
localhost:8070 <- works
localhost:8000/weatherforecast <- works
localhost:8070/api/weatherforecast <- does not work
azure app service:
*.azurewebsites.net <- works
*.azurewebsites.net/api/weatherforecast <- does not work
*.azurewebsites.net:8000/weatherforecast <-does not work
docker-compose -azure-app
version: '2.2'
services:
webapi:
image: .../isi.workflowwizard.api
networks:
- mynetwork
ports:
- "8000:8000"
loadbalancer:
image: .../loadbalancertest2
depends_on:
- webapi
networks:
- mynetwork
ports:
- 80:80
links:
- "webapi:webapi"
tty: true
networks:
mynetwork:
driver: bridge
index.html
<!DOCTYPE html>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<h1>Hello World</h1>
<p>Welcome to your first HTML document!</p>
</body>
</html>
nginx-dockerfile
FROM nginx:alpine
RUN apk update && apk upgrade && apk add bash && apk add curl && apk add openssl;
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY index.html /usr/share/nginx/html
RUN mkdir /usr/nginx
RUN openssl req -x509 -nodes -days 3650 -subj "/C=CA/ST=QC/O=Company, Inc./CN=isiGov.gov" -addext "subjectAltName=DNS:isiGov.gov" -newkey rsa:2048 -keyout /usr/nginx/ssl.key -out /usr/nginx/ssl.crt;
EXPOSE 80
EXPOSE 8090
COPY ["startup.sh", "wait-for-it.sh", "./"]
# Start Nginx
CMD "./startup.sh"
nginx.conf
error_log ./error.log debug;
upstream myapi {
server webapi:8000;
}
server {
server_name _;
listen 80;
listen 8090 ssl;
ssl_certificate /usr/nginx/ssl.crt;
ssl_certificate_key /usr/nginx/ssl.key;
underscores_in_headers on;
location /api/ {
client_max_body_size 75M;
proxy_pass http://myapi/api/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass_request_headers on;
if ($request_method ~* "(GET|POST|PUT|DELETE)") {
add_header "Access-Control-Allow-Origin" *;
}
# Preflight requests
if ($request_method = OPTIONS) {
add_header "Access-Control-Allow-Origin" *;
add_header "Access-Control-Allow-Methods" "GET, POST, PUT, DELETE, OPTIONS, HEAD";
add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type, Accept";
return 200;
}
}
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}
startup.sh
./wait-for-it.sh -t 150 webapi:8000/weatherforecast -- echo "API container is up"
nginx -g 'daemon off;'
wait-for-it.sh
#!/usr/bin/bash
# Use this script to test if a given TCP host/port are available
WAITFORIT_cmdname=${0##*/}
echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
usage()
{
cat << USAGE >&2
Usage:
$WAITFORIT_cmdname [-s] [-t timeout] [-- command args]
-s | --strict Only execute subcommand if the test succeeds
-q | --quiet Don't output any status messages
-t TIMEOUT | --timeout=TIMEOUT
Timeout in seconds, zero for no timeout
-- COMMAND ARGS Execute command with args after the test finishes
USAGE
exit 1
}
wait_for()
{
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_URL"
else
echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_URL without a timeout"
fi
WAITFORIT_start_ts=$(date +%s)
WAITFORIT_RESULT=-1
while :
do
status_code=$(curl -s -o /dev/null -w "%{http_code}" $WAITFORIT_URL)
if [[ $status_code -eq 200 ]] ; then
WAITFORIT_RESULT=0
WAITFORIT_end_ts=$(date +%s)
echoerr "$WAITFORIT_cmdname: $WAITFORIT_URL returned $status_code and is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds"
break
else
echoerr "$WAITFORIT_cmdname: $WAITFORIT_URL returned $status_code. Trying again after 10 seconds..."
fi
sleep 10
WAITFORIT_end_ts=$(date +%s)
if [[ $WAITFORIT_TIMEOUT -gt 0 ]] ; then
if [[ $((WAITFORIT_end_ts - WAITFORIT_start_ts)) -gt $WAITFORIT_TIMEOUT ]]; then
echoerr "$WAITFORIT_cmdname: Timeout of $WAITFORIT_TIMEOUT seconds has expired"
break
fi
fi
done
return $WAITFORIT_result
}
# process arguments
while [[ $# -gt 0 ]]
do
case "$1" in
*:* )
WAITFORIT_URL=(${1})
shift 1
;;
-q | --quiet)
WAITFORIT_QUIET=1
shift 1
;;
-s | --strict)
WAITFORIT_STRICT=1
shift 1
;;
-t)
WAITFORIT_TIMEOUT="$2"
if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi
shift 2
;;
--timeout=*)
WAITFORIT_TIMEOUT="${1#*=}"
shift 1
;;
--)
shift
WAITFORIT_CLI=("$@")
break
;;
--help)
usage
;;
*)
echoerr "Unknown argument: $1"
usage
;;
esac
done
if [[ "$WAITFORIT_URL" == "" ]]; then
echoerr "Error: you need to provide a url to test."
usage
fi
WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15}
WAITFORIT_STRICT=${WAITFORIT_STRICT:-0}
WAITFORIT_QUIET=${WAITFORIT_QUIET:-0}
# Check to see if timeout is from busybox?
WAITFORIT_TIMEOUT_PATH=$(type -p timeout)
WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH)
WAITFORIT_BUSYTIMEFLAG=""
if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then
WAITFORIT_ISBUSY=1
# Check if busybox timeout uses -t flag
# (recent Alpine versions don't support -t anymore)
if timeout &>/dev/stdout | grep -q -e '-t '; then
WAITFORIT_BUSYTIMEFLAG="-t"
fi
else
WAITFORIT_ISBUSY=0
fi
wait_for
WAITFORIT_RESULT=$?
if [[ $WAITFORIT_CLI != "" ]]; then
if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then
echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess"
exit $WAITFORIT_RESULT
fi
exec "${WAITFORIT_CLI[@]}"
else
exit $WAITFORIT_RESULT
fi
dockerfile - api
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["api.csproj", "."]
RUN dotnet restore "./api.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "./api.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
EXPOSE 8000
ENV ASPNETCORE_HTTP_PORTS=8000
ENTRYPOINT ["dotnet", "api.dll"]
program.cs - api
using api;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthorization();
app.MapControllers();
app.Run();
Controllers/WeatherForecastController
using Microsoft.AspNetCore.Mvc;
namespace api.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}
weatherforecast.cs
namespace api
{
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
}
api.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>2f505a21-9f8b-4d9a-b80f-7d746068ae62</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>.</DockerfileContext>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Services" />
</ItemGroup>
</Project>
giovanni freda is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.