33

I am trying to run a .NET Core 1.0.0 console application inside a docker container.
When I run dotnet run command from inside the Demo folder on my machine, it works fine; but when run using docker run -d --name demo Demo, the container exits immediately.

I tried docker logs demo to check the logs and it just shows the text from the Console.WriteLine:

Demo app running...

and nothing else.

I have uploaded the project at https://github.com/learningdockerandnetcore/Demo

The project contains Programs.cs, Dockerfile used to create Demo image, and project.json file.

Pang
  • 9,564
  • 146
  • 81
  • 122
Learning Docker
  • 583
  • 2
  • 6
  • 9
  • I'm trying to learn myself: I think you want to run it in interactive mode and probably want to add a term. `docker run -it --name demo Demo` – hdz Jul 24 '16 at 07:05
  • also you can attach to it `docker attach {container}` to get back to it if you run it in background mode(-d). You won't see the output as it already output that but you will be able to press enter for the container to exit – hdz Jul 24 '16 at 07:08

9 Answers9

23

If you switch your app to target .NET Core 2.0, you can use the Microsoft.Extensions.Hosting package to host a .NET Core console application by using the HostBuilder API to start/stop your application. Its ConsoleLifetime class would process the general application start/stop method.

In order to run your app, you should implement your own IHostedService interface or inherit from the BackgroundService class, then add it to host context within ConfigureServices.

namespace Microsoft.Extensions.Hosting
{
    //
    // Summary:
    //     Defines methods for objects that are managed by the host.
    public interface IHostedService
    {
        // Summary:
        // Triggered when the application host is ready to start the service.
        Task StartAsync(CancellationToken cancellationToken);

        // Summary:
        // Triggered when the application host is performing a graceful shutdown.
        Task StopAsync(CancellationToken cancellationToken);
    }
}

Here's a sample hosted service:

public class TimedHostedService : IHostedService, IDisposable
{
    private readonly ILogger _logger;
    private Timer _timer;

    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is starting.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero, 
            TimeSpan.FromSeconds(5));

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        _logger.LogInformation("Timed Background Service is working.");
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is stopping.");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

Then creating the HostBuilder and adding the service and other components (logging, configuration).

public class Program
{
    public static async Task Main(string[] args)
    {
        var hostBuilder = new HostBuilder()
             // Add configuration, logging, ...
            .ConfigureServices((hostContext, services) =>
            {
                // Add your services with depedency injection.
            });

        await hostBuilder.RunConsoleAsync();
    }
}
Pang
  • 9,564
  • 146
  • 81
  • 122
Feiyu Zhou
  • 4,344
  • 32
  • 36
  • 1
    Great post; much appreciated. – Daniel Jun 14 '18 at 05:41
  • Great answer. One more thing how can I run this console application by a service account? I am deploying it on Linxu container – gaurav thakur Jul 18 '18 at 04:00
  • Not sure about service account. This console app can run on windows and Linux container – Feiyu Zhou Jul 18 '18 at 04:57
  • @FeiyuZhou yes it runs on Linux container without any issues. What I meant was to set the user of the app to service account. Currently it runs by default user which doesn't have rights to write on our fileSharer servers. Any idea? – gaurav thakur Jul 19 '18 at 08:57
  • @gauravthakur Maybe you can try to run it as windows service: http://www.ben-morris.com/running-a-net-core-console-application-as-a-windows-service/ – Feiyu Zhou Jul 19 '18 at 15:30
  • this one works on newer versions of .net too, checked on .net 7 today – user2809176 Dec 23 '22 at 20:39
18

You should run your container in Interactive mode (with the -i option), but please note that the background processes will be closed immediately when you run the container, so make sure your script is run in the foreground or it simply won't work.

Pang
  • 9,564
  • 146
  • 81
  • 122
Miad Abrin
  • 952
  • 7
  • 14
  • Isn't `-i` Interactive mode - not daemon (or detached) mode? – Jay Nov 09 '16 at 10:18
  • Yes, `-d` is deamon/detached mode. – Peter Feb 20 '17 at 07:45
  • 3
    -i means that Docker provides a StandardInput to the process. That is independent from detached mode. The default behavior of Console.ReadLine if no StandardInput is present, is to return null instead of blocking. – lanwin Jul 05 '18 at 09:47
11

The only way I could get Docker/Linux to keep my .NET Core application alive was to spoof ASP.NET into hosting it for me... This is such an ugly hack!!

Doing it this way will run in Docker using the docker run -d option, so you don't have to have a live connection to keep the STDIN stream alive.

I created a .NET Core console application (not an ASP.NET app) and my Program class looks like this:

public class Program
{
    public static ManualResetEventSlim Done = new ManualResetEventSlim(false);
    public static void Main(string[] args)
    {
        //This is unbelievably complex because .NET Core Console.ReadLine() does not block in a docker container...!
        var host = new WebHostBuilder().UseStartup(typeof(Startup)).Build();
        
        using (CancellationTokenSource cts = new CancellationTokenSource())
        {
            Action shutdown = () =>
            {
                if (!cts.IsCancellationRequested)
                {
                    Console.WriteLine("Application is shutting down...");
                    cts.Cancel();
                }

                Done.Wait();
            };

            Console.CancelKeyPress += (sender, eventArgs) =>
            {
                shutdown();
                // Don't terminate the process immediately, wait for the Main thread to exit gracefully.
                eventArgs.Cancel = true;
            };

            host.Run(cts.Token);
            Done.Set();
        }
    }      
}

The Startup class:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IServer, ConsoleAppRunner>();
    }


    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
    }
}

The ConsoleAppRunner class:

public class ConsoleAppRunner : IServer
{
    /// <summary>A collection of HTTP features of the server.</summary>
    public IFeatureCollection Features { get; }

    public ConsoleAppRunner(ILoggerFactory loggerFactory)
    {
        Features = new FeatureCollection();
    }

    /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
    public void Dispose()
    {

    }

    /// <summary>Start the server with an application.</summary>
    /// <param name="application">An instance of <see cref="T:Microsoft.AspNetCore.Hosting.Server.IHttpApplication`1" />.</param>
    /// <typeparam name="TContext">The context associated with the application.</typeparam>
    public void Start<TContext>(IHttpApplication<TContext> application)
    {
        //Actual program code starts here...
        Console.WriteLine("Demo app running...");

        Program.Done.Wait();        // <-- Keeps the program running - The Done property is a ManualResetEventSlim instance which gets set if someone terminates the program.
    }
}

The only nice thing about it is that you get to use DI in your application (if you want to) - so in my use case, I am using the ILoggingFactory to handle my logging.

Edit 30th Oct 2018
This post still seems to be popular - I'd like to just point out to anyone reading my old post that it is now pretty ancient. I was basing it on .NET Core 1.1 (which was new at the time). It is likely that if you are using a newer version of.NET Core (2.0 / 2.1 or greater) that there is probably a much better way of solving this problem now. Please take time to look at some of the other posts on this thread which may not be as highly ranked as this one, but may be newer and more up-to-date.

Pang
  • 9,564
  • 146
  • 81
  • 122
Jay
  • 9,561
  • 7
  • 51
  • 72
8

UPDATE: In dotnetcore > 2.0 you can use better methods to keep your app running. In dotnet 5 you can do the following:

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

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) => { services.AddHostedService<Worker>(); });
}

Where worker inherits from IHostedService.

This method will also listen from the correct signals from docker when shutting down your application.


OLD ANSWER You can use:

Thread.Sleep(Timeout.Infinite);

See this answer:

Is Thread.Sleep(Timeout.Infinite); more efficient than while(true){}?

Tjaart
  • 3,912
  • 2
  • 37
  • 61
2

I am not sure why Console.ReadLine(); doesn't block the main thread when running a .NET Core console app in a detached docker container, but the best solution is to register a ConsoleCancelEventHandler with the Console.CancelKeyPress event.

Then you can instead block the main thread with a type of Threading WaitHandle and signal the release of the main thread when Console.CancelKeyPress is fired.

A good example code can be found here: https://gist.github.com/kuznero/73acdadd8328383ea7d5

Pang
  • 9,564
  • 146
  • 81
  • 122
2

I'm using this approach:

static async Task Main(string[] args)
{
   // run code ..

   await Task.Run(() => Thread.Sleep(Timeout.Infinite));
}
t2t
  • 1,101
  • 2
  • 12
  • 15
0

One more "dirty way" is to start your program in screen using:

screen -dmS yourprogramm
Pang
  • 9,564
  • 146
  • 81
  • 122
0

For those that what to run your .net 4.x console app in linux docker without having to specified -i and want to run it in the background, the best solution is mono.posix package, which does exactly what we are looking for, listen to linux signals.

this also applys to WebApi2 with Owin projects, or basically any console app

for most of us running containers in the background using console.read or ManualResetEventSlim or AutoResetEvent wont worked because of detached mode by docker.

The best solution is installing Install-Package Mono.Posix

here's an example:

using System;
using Microsoft.Owin.Hosting;
using Mono.Unix;
using Mono.Unix.Native;

public class Program
{
    public static void Main(string[] args)
    {
        string baseAddress = "http://localhost:9000/"; 

        // Start OWIN host 
        using (WebApp.Start<Startup>(url: baseAddress)) 
        { 
            Console.ReadLine(); 
        }

        if (IsRunningOnMono())
        {
            var terminationSignals = GetUnixTerminationSignals();
            UnixSignal.WaitAny(terminationSignals);
        }
        else
        {
            Console.ReadLine();
        }

        host.Stop();
    }

    public static bool IsRunningOnMono()
    {
        return Type.GetType("Mono.Runtime") != null;
    }

    public static UnixSignal[] GetUnixTerminationSignals()
    {
        return new[]
        {
            new UnixSignal(Signum.SIGINT),
            new UnixSignal(Signum.SIGTERM),
            new UnixSignal(Signum.SIGQUIT),
            new UnixSignal(Signum.SIGHUP)
        };
    }
}

full source blog post: https://dusted.codes/running-nancyfx-in-a-docker-container-a-beginners-guide-to-build-and-run-dotnet-applications-in-docker

-3

Using Console.ReadLine instead seems to work.

C#:

do
{
    Console.WriteLine($"Type: quit<Enter> to end {Process.GetCurrentProcess().ProcessName}");
}
while (!Console.ReadLine().Trim().Equals("quit",StringComparison.OrdinalIgnoreCase));

F#:

while not (Console.ReadLine().Trim().Equals("quit",StringComparison.OrdinalIgnoreCase)) do
    printfn "Type: quit<Enter> to end"
RusinaRange
  • 341
  • 1
  • 4
  • 18
  • This doesn't work because if no StandardInput is present (as may be the case when running in a non-interactive docker container) `Console.ReadLine()` will return null (without blocking), and `.Trim()` will throw an exception. – Wyck Jul 17 '19 at 13:20