I have a solution which contains 2 microservices (Product & Review). Each of the 2 API projects have a Dockerfile defined, here is an example of the review dockerfile:
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
ENV ConnectionString:Review="Data Source=db,1433;Initial Catalog=Review;uid=sa;pwd=TesT111t!;Integrated Security=false;MultipleActiveResultSets=True"
COPY ["Review.Api/Review.Api.csproj", "Review.Api/"]
COPY ["Review.Data/Review.Data.csproj", "Review.Data/"]
COPY ["Review.Service/Review.Service.csproj", "Review.Service/"]
RUN dotnet restore "Review.Api/Review.Api.csproj"
COPY . .
WORKDIR "/src/Review.Api"
RUN dotnet build "Review.Api.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "Review.Api.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Review.Api.dll"]
As you can see the dockerfile contains an env variable ConnectionString.
I have a breakpoint in the startup of the review api project to inspect what is inside the Configuration, here is what the file looks like:
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ReviewDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Review")), ServiceLifetime.Transient);
services.AddAutoMapper(typeof(FindProductReviews));
services.AddAutoMapper(typeof(FindReview));
services.AddAutoMapper(typeof(ReviewController));
services.AddAutoMapper(typeof(ProductController));
services.AddMediatR(typeof(FindProductReviews.Handler).Assembly);
services.AddMediatR(typeof(FindReview.Handler).Assembly);
services.AddMediatR(typeof(CreateReview.Handler).Assembly);
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapDefaultControllerRoute();
});
}
}
My solution has a docker-compose file and when running docker-compose up -d the breakpoint is hit, however when looking inside the Configuration their is no connection string which is defined like the one in the dockerfile.
I feel like im missing something small, I've looked at the documentations and cannot find what I'm missing
Application initial configuration
First of all, please confirm that, when your program starts, you are building your configuration such as below (typical setups), in order to bind it with env.variables.
// Defining a configuration that explicitly uses env. variables
public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
...
.AddEnvironmentVariables()
.Build();
or
// The default builder already adds env. variables
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
...
Docker
Please, replace ConnectionString:Review by ConnectionString__Review (reference here).
If you want to keep on setting the env.variable in your Dockerfile, you can move it to the last section final, such as:
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENV ConnectionString__Review="Data Source=db,1433;Initial Catalog=Review;uid=sa;pwd=TesT111t!;Integrated Security=false;MultipleActiveResultSets=True"
ENTRYPOINT ["dotnet", "Review.Api.dll"]
However, to enhance decoupling, you may prefer to set the variable in your docker-compose file.
You may also consider using the appsettings JSON files, since they allow much flexibility across environments.
Multiple connection configurations
If your services are able to access shared configurations, but also have their own, then, you can specify proper names to each configuration, to ensure that any mistake happens :)
You only define the ENV variable in the build image. If you follow the FROM chain, the final image is built FROM base, and COPY --from=build, but the COPY only copies files and not environment variables or other metadata.
Your question title and tags hint at a Compose-based setup. Connection information like the database host name and (especially) credentials need to be configured at deployment time, not part of the image. I'd remove this line from your Dockerfile entirely, and instead include it in your docker-compose.yml:
version: '3.8'
services:
db: { ... }
app:
environment:
- ConnectionString:Review=Data Source=db,1433;Initial Catalog=Review;uid=sa;pwd=TesT111t!;Integrated Security=false;MultipleActiveResultSets=True
...
Related
When I try to run my ASP.Net Core web api in a docker container, the application exitst early.
My Dockerfile:
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 5000
ENV ASPNETCORE_URLS=http://*:5000
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY . ./
RUN dotnet restore
FROM build AS publish
RUN dotnet publish -c Release -o /out/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /out/publish .
ENTRYPOINT ["dotnet", "WebService.dll"]
But when i try to run this container the only log thats displayed is this
2022/08/11 09:11:20.802 | Warn | | Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository| Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
and the container exitst with exit code 139.
What am I doing wrong?
Th data Protection stores keys in the HOME directory (/root/.aspnet/DataProtection-Keys) so when the container restart the keys will be lost, and it might crash the service.
Add a Volume to
root/.aspnet/DataProtection-Keys
To check if the error is something else, please build in the debug mood
RUN dotnet publish --output /app/ --configuration Debug
I was planning to use Azure Devops to do the CI/CD for my dockerized .NET Core API.
My API now have appsettings.json and appsettings.Development.json.
Below are my Dockerfile:
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS base
WORKDIR /app
EXPOSE 80
ENV ASPNETCORE_ENVIRONMENT Development
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /src
COPY ["Api/Api.csproj", "Api/"]
RUN dotnet restore "Api/Api.csproj"
COPY . .
WORKDIR "/src/Api"
RUN dotnet build "Api.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "Api.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Api.dll", "--environment=Development"]
As can seen, I set the environment at ASPNETCORE_ENVIRONMENT and also the ENTRYPOINT ["dotnet", "Api.dll", "--environment=Development"]. However how can I make it flexible in Azure Devops pipeline?
For example having different environments like development and production. So let say I have another appsettings.Production.json, the project would automatically pick up this file instead of appsettings.Development.json if I set the environment to Production.
Is there anyway to do this or am I understanding this whole flow wrong?
Thank you.
Check the answer in this case: Inject env variable into build stage of image
You can set an ARG var_name and reference ENV to the ARG variables. Then you can replace those variables when docker build the image $ docker build --build-arg var_name=$(VARIABLE_NAME)
For example the add ARG in dockerfile, and have the ENV variable refer to it:
ARG SECRET
ENV ASPNETCORE_ENVIRONMENT=$SECRET
You can use dock build task and dock push task separately, as buildandpush command cannot accept arguments. And set a variable SECRET in your pipeline.
The set the Build Arguments SECRET= $(SECRET) to replace the ARG SECRET
I have a dockerized asp.net Core application trying to connect to a mySql Database. Both are running inside a docker-compose. When I test the connection to a local Database without Docker, my code is working fine, but when I deploy it on a Vm inside docker-compose and that I call one of my controller, I get this error : System.ArgumentNullException: Value cannot be null. (Parameter 'connectionString') .
Here is my docker-compose :
version: '3'
services:
dbgil:
container_name: dbgil
image: mysql
restart: always
ports:
- 3306:3306
environment:
MYSQL_ROOT_PASSWORD: root
volumes:
- dbdata:/var/lib/mysql
webserver:
depends_on:
- dbgil
image: lionelquirynen/gillesquirynensys:latest
ports:
- "8021:80"
links:
- dbgil
volumes:
dbdata:
And here is my startup in my asp.net core application :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using GillesQuirynenSys.Data;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace GillesQuirynenSys
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<MySqlDbContext>(options =>
options.UseMySql("server=localhost;port=3306;database=test;user=root;password=root;convert zero datetime=True"));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, MySqlDbContext context)
{
context.Database.EnsureCreated();
var databaseCreator = context.GetService<IRelationalDatabaseCreator>();
databaseCreator.CreateTables();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
I first had my connection string in my appsettings.json but I was getting the same error so I tried to hardcode it but it did not change anything. Has anyone any ideas? It seems like my configuration is working fine (at least, it is locally without Docker).
PS: Here is the DockerFile of my asp.net Core application before pushing it to DockerHub :
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["GillesQuirynenSys/GillesQuirynenSys.csproj", "GillesQuirynenSys/"]
RUN dotnet restore "GillesQuirynenSys/GillesQuirynenSys.csproj"
COPY . .
WORKDIR "/src/GillesQuirynenSys"
RUN dotnet build "GillesQuirynenSys.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "GillesQuirynenSys.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "GillesQuirynenSys.dll"]
I'm glad my answer helped.
So basicaly in the docker container, the server name of MySQL db is the same as the service name declared in docker-compose.yml file.
One more note to your code:
Instead of this:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<MySqlDbContext>(options =>
options.UseMySql("server=localhost;port=3306;database=test;user=root;password=root;convert zero datetime=True"));
}
-> where you hard-code your connection string
Try this:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<MySqlDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("DefaultConnection")));
}
And adjust your appsettings.json file - for production (docker container) build, you will have:
{
"ConnectionStrings": {
"DefaultConnection": "server=dbgil;port=3306;database=test;user=root;password=root;convert zero datetime=True"
}
...
}
And in appsettings.Development.json, you will have a connection string for Development mode (your localhost):
{
"ConnectionStrings": {
"DefaultConnection": "server=localhost;port=3306;database=test;user=root;password=root;convert zero datetime=True"
}
...
}
Try using this, it works:
"ConnectionStrings": {
"ShopOnlineConnection": "server:(localdb)\\MSSQLLocalDB;database=ShopOnline;Trusted_Connection:True;"
},
I'm just getting started with Docker and have installed docker for windows.
The basic setup of docker is correct and i have been able to debug a simple Asp.Net Core app which is deployed to a container from within Visual studio (using the standard 'Run' command targeting docker).
The problem i'm having is being able to hit the endpoint hosted from within the container without using localhost i.e. using the IP of the container. I need this as i'm intending to hit the endpoint from a xamarin app.
After doing some reading, it seems i need to 'publish' the port that the application is running, in this case port 5000, but i can't seem to find where to configure visual studio to do this.
Using postman or a web browser to hit the endpoint results in the same response Empty_Response error.
I'm hoping someone can point me in the right direction
My Dockerfile:
FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-stretch-slim AS base
WORKDIR /app
EXPOSE 5000
ENV ASPNETCORE_URLS http://<container ip>:5000
FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS build
WORKDIR /src
COPY ["ItemCheckout/ItemCheckout.csproj", "ItemCheckout/"]
RUN dotnet restore "ItemCheckout/ItemCheckout.csproj"
COPY . .
WORKDIR "/src/ItemCheckout"
RUN dotnet build "ItemCheckout.csproj" -c Release -o /app
FROM build AS publish
RUN dotnet publish "ItemCheckout.csproj" -c Release -o /app
FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "ItemCheckout.dll"]
Startup.cs:
public class Startup
{
private static readonly LoggerFactory _loggerFactory = new LoggerFactory(new []{new DebugLoggerProvider()});
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<ItemCheckoutDbContext>(o =>
{
o.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
o.UseLoggerFactory(_loggerFactory);
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}
program.cs:
public class Program
{
public static async Task Main(string[] args)
{
await CreateWebHostBuilder(args).Build().RunAsync();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseUrls("http://<container ip>:5000")
.UseStartup<Startup>();
}
Output when running:
Hosting environment: Development
Content root path: /app
Now listening on: http://<container ip>:5000
Application started. Press Ctrl+C to shut down.
EDIT: Updated program.cs as per #MindSwipe's suggestion, however i am still getting the same result
Port mapping must be specified during docker run command. EXPOSE command within dockerfile normally used for documentation purposes.
Solution: in your Visual Studio project file add the following:
<PropertyGroup>
<DockerfileRunArguments>-p 5000:5000</DockerfileRunArguments>
</PropertyGroup>
References:
https://learn.microsoft.com/en-us/visualstudio/containers/container-msbuild-properties?view=vs-2019
https://docs.docker.com/engine/reference/builder/#expose
OK - from what I see - you're confusing the port on the container with the port on the host.
You have 2 sides to port mapping in Docker - the host side and the client/container side.
There's a couple of ways to solve the issue that you can't connect.
With the container running (F5 debug, command line) - execute the following command:
"docker ps -a"
You will see a list of running containers; there's a column called "Ports". Find the entry for your container - probably the solutionname:dev. Look at the ports column, you will see AA -> BB.
AA is the port you need in your browser - that is your host port - so http://localhost:AA, or http://IP-Address:AA/
BB is the listening port on the container - in your case 5000.
Does this help?
EDIT:
change this line: ENV ASPNETCORE_URLS http://:5000
to ENV ASPNETCORE_URLS http://+:5000
This is relative to the container - not the host.
I am running a .net core app in a docker container. Here is my docker file (just to build a dev environment):
FROM microsoft/dotnet:1.0.1-sdk-projectjson
ENV ASPNET_ENV Development
COPY bin/Debug/netcoreapp1.0/publish/ /root/
EXPOSE 5000/tcp
ENTRYPOINT dotnet /root/AVP.WebApi.dll
I have an appSettings.Development.json file in the /publish/ folder. If I build and run this Docker file, I end up with a strange issue where the .NET app can't find the appsettings it needs to start (they're in the appSettings.Development.json file).
I have confirmed that from the command line in windows if I run dotnet publish/AVP.WebAPI.dll the app throws the same exceptions for missing configuration settings. However if I cd to /publish and run dotnet AVP.WebAPI.dll the app starts and runs just fine.
Any ideas how to change my docker file so it will run the app properly with the appSettings.Development.json values? (I've also tried to run the app with all the values copied into the regular appSettings.json files with no luck)
I've also tried running a the COPY command to / instead of /root and doing dotnet AVP.WebApi.dll as the entry point, but that results in the project being unable to find dependencies.
Try replacing this line:
ENV ASPNET_ENV Development
With this:
ENV ASPNETCORE_ENVIRONMENT Development
Your original Environment Variable name was used in older .NET Core, but has been changed. It can be a pain finding tutorials, etc. for .NET Core because of all of the changes that have happened since it first started!
Don't get me started on project.json files!
More info:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/environments
As a follow up to everyone (I posted this is a comment originally), this is what ended up fixing it:
From what I can tell it looks like dotnet expects the appsettings files to be in the same directory it is run from. So I added COPY bin/Debug/netcoreapp1.0/publish/appsettings.json /appsettings.json to the dockerfile (this line copies the appsettings file to the directory below /root/ where I copied the publish folder to). Everything started working at this point. It appears that the dotnet executable runs from the directory below /root/ so it couldn't find it before, now that appsettings is in the same folder, it's all happy.
It late, but i think my solution will help other.
Step 1. Put appseting in folder "Settings/appsettings.json".
Here is my appsettings.json
{
"ConnectionString": "Data Source=local;Initial Catalog=mydb;User Id=username;Password=myStr0ngPassword#;"
}
Step 2. Edit code from asp netcore.
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
namespace Repos.Configs
{
public static class ConfigurationManager
{
public static string currentPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
public static IConfiguration AppSetting { get; }
static ConfigurationManager()
{
AppSetting = new ConfigurationBuilder()
.SetBasePath(currentPath)
.AddJsonFile("Settings/appsettings.json") // your path here
.Build();
}
}
}
And use AppSetting.
var connectionString = ConfigurationManager.AppSetting["ConnectionString"];
Step 3. now u must config your dockerfile, in my case, create by visual studio in linux container.
FROM mcr.microsoft.com/dotnet/core/aspnet:3.0-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster AS build
WORKDIR /src
COPY ["Api/Api.csproj", "Api/"]
COPY ["Repos/Repos.csproj", "Repos/"]
COPY ["DataContext/DataContext.csproj", "DataContext/"]
RUN dotnet restore "Api/Api.csproj"
COPY . .
WORKDIR "/src/Api"
RUN dotnet build "Api.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "Api.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Api.dll"]
Step 4. Build image.
docker build -t tagForProject .
Step 5. Map volume and run your container from image.
docker run -p 44382:80 --volume c:\Docker\Volumes\MyProjectNetCore:/app/Settings --name nameWillDisplayInDockerDashboard -d tagForProject
OK, one problem here, because docker will override c:\Docker\Volumes\MyProjectNetCore to /app/Settings. So, you must put appseting.json to c:\Docker\Volumes\MyProjectNetCore.
If not, your app cant read appsetting because it not exist in docker volume.
Step 6. Restart your app in docker dashboard and see it work.
There are three problems i can think of why it can't find the appsettings:
They are not in the right folder in the container (did you copy the publish folder and does the publish folder contain the appsetting
You did not define using appsettings for the environment in the StartupClass: appSettings.${Environment}.json
It works locally because windows filesystem is case-insensitive and linux is case sensitive and thus it can't find the file. (check your capitalization).
I use docker-compose with .net 5, I put this on my yaml :
container_name: heroapi
environment:
- ASPNETCORE_ENVIRONMENT=alpha
for use appsettings.alpha.json file and it's work
Just came across this issue myself and the problem was the file is not being copied by default.
This was fixed by editing the .csproj file and adding :
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
This can also be done through UI by right clicking the appsettings file within visual studio, selecting properties and setting sopy to directory to "Always".
My problem was, through all this docker container stuff.. I never built the project in Release Mode, so the appsettings.json file in /bin/release/publish was very old.
I just hit this issue myself, and Sam's answer did solve it. Just to propose a different solution, you can also set the WORKDIR /root directive in the Dockerfile and then use ENTRYPOINT dotnet AVP.WebApi.dll which will call dotnet in the context of the root directory.
Full Dockerfile:
FROM microsoft/dotnet:1.0.1-sdk-projectjson
ENV ASPNET_ENV Development
COPY bin/Debug/netcoreapp1.0/publish/ /root/
WORKDIR /root
EXPOSE 5000/tcp
ENTRYPOINT dotnet AVP.WebApi.dll
for compose:
environment:
- ASPNETCORE_ENVIRONMENT=Production
For Docker:
ENV ASPNETCORE_ENVIRONMENT: "Production"
there is a quick and easy way to Update/edit the AppSettings.json file:
for example my AppSettings.json file :
{
"GrpcOption": {
"ServerAddress": "oldurl"
},
}
now i can modify it by -e command
docker run -e "GrpcOption:ServerAddress=newurl" [imagename]