5

I'm pretty new with docker, but running up against a wall trying to get my implementation to work through Docker Compose.

Docker Compose File

version: "3.4"
services:
    app:
        build:
            context: .
            dockerfile: Dockerfile
        ports:
            - 5000:5000
            - 5001:5001

Dockerfile

# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
WORKDIR /app
    
# Install EF tools      
RUN dotnet tool install --global dotnet-ef
ENV PATH="${PATH}:/root/.dotnet/tools"  

# Generate Certificates
RUN dotnet dev-certs https -ep ${HOME}/.aspnet/https/aspnetapp.pfx -p TestPassword
RUN dotnet dev-certs https --trust

# Copy everything else and build
COPY . ./
COPY Setup.sh Setup.sh
RUN dotnet restore
RUN dotnet build -c Release -o out

# Build runtime image
RUN chmod +x ./Setup.sh
CMD /bin/bash ./Setup.sh

Setup.sh Entry Point

#!/bin/bash

set -e
run_cmd="dotnet run --project Artis.Merchant.API/Artis.Merchant.API.csproj --launch-profile Artis.Merchant.API"

until dotnet ef database update --project Artis.Models/Artis.Models.csproj; do
>&2 echo "SQL Server is starting up"
sleep 1
done

>&2 echo "SQL Server is up - executing command"
exec $run_cmd

Launch Settings for Project

  "profiles": {
    "Artis.Merchant.API": {
    "commandName": "Project",
    "launchBrowser": true,
    "launchUrl": "swagger",
    "environmentVariables": {
      "ASPNETCORE_ENVIRONMENT": "Local"
    },
  "applicationUrl": "https://localhost:5001;http://localhost:5000"
},

So both my http or https endpoints seem to be unreachable when launched through Docker. The CLI output is the exact same, so it appears to be up and running through Docker. Any ideas what I'm doing wrong?

Locally running dotnet run --project Artis.Merchant.API/Artis.Merchant.API.csproj --launch-profile Artis.Merchant.API

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Local
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\Users\ddrob\source\Artis.Merchant.API

The above works fine I and access it as normal through https://localhost:5001 or http://localhost:5000

Output from Docker after running docker compose up

artis_api-app-1  | info: Microsoft.Hosting.Lifetime[14]
artis_api-app-1  |       Now listening on: http://localhost:5000
artis_api-app-1  | info: Microsoft.Hosting.Lifetime[14]
artis_api-app-1  |       Now listening on: https://localhost:5001
artis_api-app-1  | info: Microsoft.Hosting.Lifetime[0]
artis_api-app-1  |       Application started. Press Ctrl+C to shut down.
artis_api-app-1  | info: Microsoft.Hosting.Lifetime[0]
artis_api-app-1  |       Hosting environment: Local
artis_api-app-1  | info: Microsoft.Hosting.Lifetime[0]
artis_api-app-1  |       Content root path: /app/Artis.Merchant.API

Been testing by just making an HTTP GET call to https://localhost:5001/api/health which should just return a 200. Works fine running locally, Docker returns either a socket hang up if accessed through non-https, and client network socket disconnected before secure TLS connection was established when accessed through https

EDIT

For more insight, here are the outputs from docker ps and docker inspect artis_api-app

CONTAINER ID   IMAGE                            COMMAND                  CREATED             STATUS          PORTS                              NAMES
4b71ffc2c5a1   artis_api-app                    "/bin/sh -c '/bin/ba…"   43 minutes ago      Up 23 minutes   0.0.0.0:5000-5001->5000-5001/tcp   artis_api-app-1
[
    {
        "Id": "sha256:505685ec26fb58cc87fe4ec5166f2b3c0978b251953f5e9d431776ad036fc839",
        "RepoTags": [
            "artis_api-app:latest"
        ],
        "RepoDigests": [],
        "Parent": "",
        "Comment": "buildkit.dockerfile.v0",
        "Created": "2022-09-14T18:40:33.7696322Z",
        "Container": "",
        "ContainerConfig": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": null,
            "Cmd": null,
            "Image": "",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": null
        },
        "DockerVersion": "",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.dotnet/tools",
                "ASPNETCORE_URLS=",
                "DOTNET_RUNNING_IN_CONTAINER=true",
                "DOTNET_VERSION=6.0.9",
                "ASPNET_VERSION=6.0.9",
                "DOTNET_GENERATE_ASPNET_CERTIFICATE=false",
                "DOTNET_NOLOGO=true",
                "DOTNET_SDK_VERSION=6.0.401",
                "DOTNET_USE_POLLING_FILE_WATCHER=true",
                "NUGET_XMLDOC_MODE=skip",
                "POWERSHELL_DISTRIBUTION_CHANNEL=PSDocker-DotnetSDK-Debian-11"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "/bin/bash ./Setup.sh"
            ],
            "ArgsEscaped": true,
            "Image": "",
            "Volumes": null,
            "WorkingDir": "/app",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": null
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 6245959471,
        "VirtualSize": 6245959471,
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/uqv91vlzccv2h5y9g9mzcbl2a/diff:/var/lib/docker/overlay2/dewh3h889lvrnae1qq1gaucga/diff:/var/lib/docker/overlay2/21fioiit0habui6whx5x56e4w/diff:/var/lib/docker/overlay2/xgcecdok1kdh5je5ti1xuyylx/diff:/var/lib/docker/overlay2/yxu9wlvfmwn5rp9lth9f9ff8s/diff:/var/lib/docker/overlay2/imqsg5pxz3xkzxsaucymd3xv5/diff:/var/lib/docker/overlay2/etcmf1dskml8g13hg0eeqgtrq/diff:/var/lib/docker/overlay2/3orhtkibg5z6eeosvazrsjjwx/diff:/var/lib/docker/overlay2/e903ebed1d7345de8bd1780dbb132091b2930ddaa014ef4f399ce87be1e105d4/diff:/var/lib/docker/overlay2/d49d1b24c5821a65689ef573e9f6e7f4562cc2cd7fb4d1e8f2f6144f31cbb1dc/diff:/var/lib/docker/overlay2/bbf9acaefd8441bb31972a56526870d63995056b59f59166560007d0a114b2f9/diff:/var/lib/docker/overlay2/2306e276d00d98d2aaff2af655cba48efe5b4c0c072f54b6883818a5063d2623/diff:/var/lib/docker/overlay2/7ab5f58b84e23d093901394612c6cc71f8b704f8408fcaab10ed9c2b799e71e4/diff:/var/lib/docker/overlay2/2cd518af44aa8bae9adaa6460d9c88a761f2fdccdd36b69ddcd4809d26fbb2a0/diff:/var/lib/docker/overlay2/ea6cf0ad92836e7d871430c029e19c688f5b7caeaee475f1b7fb18b215511cd9/diff:/var/lib/docker/overlay2/892c98581f39f02c87c8ca05c73b98bdc29ab6862ebb2e70ee8c7006d1f90ccf/diff",
                "MergedDir": "/var/lib/docker/overlay2/u0v4b4saadkhe84ztlc2blsnj/merged",
                "UpperDir": "/var/lib/docker/overlay2/u0v4b4saadkhe84ztlc2blsnj/diff",
                "WorkDir": "/var/lib/docker/overlay2/u0v4b4saadkhe84ztlc2blsnj/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:b45078e74ec97c5e600f6d5de8ce6254094fb3cb4dc5e1cc8335fb31664af66e",
                "sha256:5ec686cbc3c7aea55c4f80c574962f120e5f81ed0b7ccacbbde51f8d819f8247",
                "sha256:e487c4dad54c656811ed2064a764f7240bb3b5936d497ec757991f9344be616d",
                "sha256:64d665f70cc187b1c5a5c1fc8d0a4431ea0f0069c1985ce330c55523466c22b2",
                "sha256:efccb7d95dee67a40c57966066356e47a301cc6028db923ab58fe4f21564fefb",
                "sha256:b4d89feca49ab5217ab7d09079ab1f07c618570d0822ad74ccce634313cb0c91",
                "sha256:d5471ff23747a10089f58397f39c2f66c6c8937687dd2b3592ab6fe09c6756d0",
                "sha256:08affa1f86cb7d7262620e2517bc3308598db5d047135c5b7db00994d22f6701",
                "sha256:6f1bf9eb1c14548bc0b119efb283637880394c2cae2117de367238ab3b7fcb80",
                "sha256:be544cd1ea837e49241d66815afaeddfe79b3d869972f64451e3c2dcdf8c10eb",
                "sha256:7c2ba258f59487f4dacf912a4f2c0a7598e2b4082283555fd5ca127da145cdc9",
                "sha256:3bcd28ee7f58f12baceeb1ab2c097675ca35e66f3e350869a73d5bc508fcfd57",
                "sha256:11f2bbe76890bfd069e0f1b75dd4acd35dc33f071f061e5c6751e5af7723e897",
                "sha256:d5f7661f5ac2f07ba5836972af96f89358381cbb5399342ef6fd03b6306fe000",
                "sha256:5879a59c7c2aba6ffcc9535592116cc92e7efd4d7acff9f7716e1e2ac167c3e8",
                "sha256:fdedee5f422b0f13237dd54f9894c64c4323f48d7d06e10a7640991ce9dd62ce",
                "sha256:01104bf88915fea5e113a37e57b450afa15ec647816e61b17bea508082a8ee32"
            ]
        },
        "Metadata": {
            "LastTagTime": "0001-01-01T00:00:00Z"
        }
    }
]

I noticed the outputs are different when I inspect artis_api-app-1 instead of the Image named artis_api-app. I figured the only relevant portion of the former is the network settings output.

        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "174a63cebce5ca8ff7c6b218a73c90e5b813caad60b008777a1fdb031c595ced",
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "Ports": {
                "5000/tcp": [
                    {
                        "HostIp": "0.0.0.0",
                        "HostPort": "5000"
                    }
                ],
                "5001/tcp": [
                    {
                        "HostIp": "0.0.0.0",
                        "HostPort": "5001"
                    }
                ]
            },
            "SandboxKey": "/var/run/docker/netns/174a63cebce5",
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "",
            "Gateway": "",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "",
            "IPPrefixLen": 0,
            "IPv6Gateway": "",
            "MacAddress": "",
            "Networks": {
                "artis_api_default": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": [
                        "artis_api-app-1",
                        "app",
                        "4b71ffc2c5a1"
                    ],
                    "NetworkID": "dc60832ac2b09450067b3edeb1cd944ad9c7c4805a674da5dba456654db49125",
                    "EndpointID": "cd04c2b9261b8ec3a095a6c3db6001484e41a41ca0dd3c32a72c093cf3787e7b",
                    "Gateway": "172.22.0.1",
                    "IPAddress": "172.22.0.3",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:16:00:03",
                    "DriverOpts": null
                }
            }
        }

EDIT 2

Here is my initial Program.cs entry point since I've had some questions regarding that.

    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.ConfigureAppConfiguration((context, configBuilder) =>
                    {
                        string assemblyName = Assembly.GetExecutingAssembly().GetName().Name;
                        string envName = context.HostingEnvironment.EnvironmentName;
                        configBuilder.Sources.Clear();
                        configBuilder.AddJsonFile($"{assemblyName}.appsettings.json", optional: false, reloadOnChange: true);
                        configBuilder.AddJsonFile($"{assemblyName}.appsettings.{envName}.json", optional: true, reloadOnChange: true);
                    });
                    webBuilder.UseStartup<Startup>();
                });
    }

Answer

Along with the accepted answer I also needed to include a Kestrel configuration in my appsettings.json file such as this:

"Kestrel": {
    "Endpoints": {
      "Http": {
        "Url":  "http://0.0.0.0:5000"
      },
      "Https": {
        "Url": "https://0.0.0.0:5001"
      }
    },
    "EndpointDefaults": {
      "Url": "https://0.0.0.0:5001",
      "Protocols": "Http1"
    }
  }
1
  • Worth noting that you can also specify the appsettings.json config entries for Kestrel as environment variables in your docker-compose.yml file if that works better for you, eg: - ASPNETCORE_Kestrel__Endpoints__Http__Url=http://0.0.0.0:5000 Same function, just different presentation. Commented Apr 19, 2023 at 14:42

3 Answers 3

6
+50

Please, try providing a value for the environment variable ASPNETCORE_URLS when running your container.

For example, in docker-compose:

version: "3.4"
services:
    app:
        build:
            context: .
            dockerfile: Dockerfile
        ports:
            - 5000:5000
            - 5001:5001
        environment:
            - ASPNETCORE_ENVIRONMENT=Local
            - ASPNETCORE_URLS=https://+:5001;http://+:5000
            # please, review the provided path, according to your
            # setup I am unsure whether it is exact or not
            - ASPNETCORE_Kestrel__Certificates__Default__Path=${HOME}/.aspnet/https/aspnetapp.pfx
            # consider use an env varible to provide the password, to avoid
            # putting under version control system sensitive information
            - ASPNETCORE_Kestrel__Certificates__Default__Password=TestPassword

This will allow your app to listen in all the network interfaces available: on the contrary, it will only be accesible through localhost but be aware that localhost in that context is the container itself.

You could provide an analogous information in the Dockerfile as well.

Please, notice that in order to support HTTPS I included information about the location of the pfx bundle and the corresponding password you used when building the image. It is necessary for that purpose: consider read the provided Microsoft documentation.

A final word about the certificates: as you can see in the mentioned documentation, the certificates used by the application are usually mounted through a docker volume to avoid including it in your Docker image - making it public in practice. It is fine for a POC but please, never store your production certificates and passwords like this.

Sign up to request clarification or add additional context in comments.

10 Comments

Still no luck. With those environment changes I'm still getting the same results. Not sure if I'm suppose to be specifying the port when trying to access it from the host side or not. But my results are: GET http://localhost/api/health -> Error: connect ECONNREFUSED 127.0.0.1:80. GET https://localhost/api/health -> Error: connect ECONNREFUSED 127.0.0.1:443. GET http://localhost:5000/api/health -> Error: socket hang up. GET https://localhost:5001/api/health -> Error: Client network socket disconnected before secure TLS connection was established
Thank you very much for the feedback Dillon. The errors related to the connection through ports 80 and 443, ECONNREFUSED, make perfect sense, because you are actually exposing ports 5000 and 5001, respectively. The other errors indicate in a nutshell that the client is not receiving any server response. Excuse for asking but did you recreate the docker compose resources? I mean, did you execute docker-compose down and docker-compose up -d with the new configuration? Did your log output change?
I deleted the entire container / images before running through it again with your suggestions. I'll log the output and post it on github or something.
Thank you for sharing Dillon. Please, could you provide the output of docker inspect for the artis_api-app-1 container as well?
|
5

Your application is listen for connection inside container (localhost) only.

Now listening on: http://localhost:5000

To be able to pass-through the port, the application should listen external network adapter. It is enough to listen http://0.0.0.0:5000.

1 Comment

Thank you! This ended up being this issue, along with needing to include Kestrel configuration in my environment files
1

Having the same issue previously caused by port setting in Programm.cs Please check Interface IWebHostBuilder you might have specified specific port like this

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
      WebHost.CreateDefaultBuilder(args)
          .UseStartup<Startup>()
          .UseWebRoot("")
          .UseUrls(urls: "http://localhost:5000");

and change it with this

public static IHostBuilder CreateWebHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });

1 Comment

Edited my original question to show my Program.cs file. I don't explicitly set any of my runtime properties there. My environment configurations are in appsettings.json files. And my runtime properties are all in a launchSettings.json. in my Setup.sh entry point for docker I specify to use my launch profile Artis.Merchant.API which should point it to http://localhost:5000 and https://localhost:5001

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.