Skip to main content
Redhat Developers  Logo
  • Products

    Featured

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat OpenShift AI
      Red Hat OpenShift AI
    • Red Hat Enterprise Linux AI
      Linux icon inside of a brain
    • Image mode for Red Hat Enterprise Linux
      RHEL image mode
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • Red Hat Developer Hub
      Developer Hub
    • View All Red Hat Products
    • Linux

      • Red Hat Enterprise Linux
      • Image mode for Red Hat Enterprise Linux
      • Red Hat Universal Base Images (UBI)
    • Java runtimes & frameworks

      • JBoss Enterprise Application Platform
      • Red Hat build of OpenJDK
    • Kubernetes

      • Red Hat OpenShift
      • Microsoft Azure Red Hat OpenShift
      • Red Hat OpenShift Virtualization
      • Red Hat OpenShift Lightspeed
    • Integration & App Connectivity

      • Red Hat Build of Apache Camel
      • Red Hat Service Interconnect
      • Red Hat Connectivity Link
    • AI/ML

      • Red Hat OpenShift AI
      • Red Hat Enterprise Linux AI
    • Automation

      • Red Hat Ansible Automation Platform
      • Red Hat Ansible Lightspeed
    • Developer tools

      • Red Hat Trusted Software Supply Chain
      • Podman Desktop
      • Red Hat OpenShift Dev Spaces
    • Developer Sandbox

      Developer Sandbox
      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared Openshift and Kubernetes cluster.
    • Try at no cost
  • Technologies

    Featured

    • AI/ML
      AI/ML Icon
    • Linux
      Linux Icon
    • Kubernetes
      Cloud icon
    • Automation
      Automation Icon showing arrows moving in a circle around a gear
    • View All Technologies
    • Programming Languages & Frameworks

      • Java
      • Python
      • JavaScript
    • System Design & Architecture

      • Red Hat architecture and design patterns
      • Microservices
      • Event-Driven Architecture
      • Databases
    • Developer Productivity

      • Developer productivity
      • Developer Tools
      • GitOps
    • Secure Development & Architectures

      • Security
      • Secure coding
    • Platform Engineering

      • DevOps
      • DevSecOps
      • Ansible automation for applications and services
    • Automated Data Processing

      • AI/ML
      • Data Science
      • Apache Kafka on Kubernetes
      • View All Technologies
    • Start exploring in the Developer Sandbox for free

      sandbox graphic
      Try Red Hat's products and technologies without setup or configuration.
    • Try at no cost
  • Learn

    Featured

    • Kubernetes & Cloud Native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • Java
      Java icon
    • AI/ML
      AI/ML Icon
    • View All Learning Resources

    E-Books

    • GitOps Cookbook
    • Podman in Action
    • Kubernetes Operators
    • The Path to GitOps
    • View All E-books

    Cheat Sheets

    • Linux Commands
    • Bash Commands
    • Git
    • systemd Commands
    • View All Cheat Sheets

    Documentation

    • API Catalog
    • Product Documentation
    • Legacy Documentation
    • Red Hat Learning

      Learning image
      Boost your technical skills to expert-level with the help of interactive lessons offered by various Red Hat Learning programs.
    • Explore Red Hat Learning
  • Developer Sandbox

    Developer Sandbox

    • Access Red Hat’s products and technologies without setup or configuration, and start developing quicker than ever before with our new, no-cost sandbox environments.
    • Explore Developer Sandbox

    Featured Developer Sandbox activities

    • Get started with your Developer Sandbox
    • OpenShift virtualization and application modernization using the Developer Sandbox
    • Explore all Developer Sandbox activities

    Ready to start developing apps?

    • Try at no cost
  • Blog
  • Events
  • Videos

How to deploy .NET apps as systemd services using containers

January 17, 2023
Tom Deseyn
Related topics:
.NETContainersLinuxKubernetes
Related products:
Red Hat Enterprise Linux

Share:

    In this article, we will deploy a .NET app as a systemd service. Instead of installing the application directly on the system, we will take a different approach by using a container instead.

    Why use a container?

    When we deploy an application as a systemd service, we need to solve problems that go beyond starting the application. Containers solve problems such as: how the application gets distributed, how upgrades are managed, and how native dependencies are provided.

    Container images are distributed through registries. Images are versioned using tags, allowing for easy upgrades and including all the dependencies. When deploying to Kubernetes, packing up your .NET application as a container for systemd gives it the benefits of being self-contained and universally deployable.

    Creating an application

    As an example for this article, we will use an application based on the Worker template. The Worker template is a console application based on Microsoft.Extension.Hosting and provides the configuration and dependency injection services you know from ASP.NET Core. Worker is meant for long-running applications that aren't web applications. You can learn more about the Worker template in Worker Services. What we do in this article also applies to ASP.NET Core.

    Create the application as follows:

    $ dotnet new worker -o worker
    $ cd worker

    If you open up the application folder, you'll find the Worker.cs file, which implements a simple long-running job that prints a message to the console once a second.

    Execute dotnet run to see the application in action. When you've seen a couple of messages, press Ctrl-C to terminate the application.

    The .NET Core 3.0 added support for systemd services. Specifically, the application host (from Microsoft.Extension.Hosting) can notify systemd upon startup. For a web application, notifying systems indicates that the application is ready to serve requests. In our example, the notification means that all the hosted services, such as the Worker class, have started for our worker.

    If you want a BackgroundService such as Worker.cs to do asynchronous initialization that affects readiness, you can allow time for that activity by overriding the base StartAsync method, as shown in the following code:

    public async override Task StartAsync(CancellationToken cancellationToken)
    {
      // Perform startup.
      System.Console.WriteLine("Starting");
      await Task.Delay(500, cancellationToken);
      System.Console.WriteLine("Started");
    
      // Call base method.
      await base.StartAsync(cancellationToken);
    }

    In .NET 7, systemd support was extended to include .NET running in a container managed by systemd. If you are using an earlier version of .NET, systemd can run your container, but it won't know when your application is fully ready.

    To enable systemd integration, add the Microsoft.Extensions.Hosting.Systemd package:

    $ dotnet add package Microsoft.Extensions.Hosting.Systemd

    Next, call UseSystemd on your application's IHostBuilder:

    IHost host = Host.CreateDefaultBuilder(args)
      .ConfigureServices(services =>
      {
          services.AddHostedService<Worker>();
      })
    + .UseSystemd()
      .Build();

    You can add this code to any .NET application to make it runnable with systemd. If the application doesn't execute from systemd, the UseSystemd call is a no-op.

    Creating a container image

    Now we're going to create a container image. There are a number of ways you can do this. You can hand-craft a Containerfile (Dockerfile) based on Microsoft's Containerize a .NET app tutorial. You can use the new SDK feature that builds an image for you: as described in Announcing built-in container support for the .NET SDK. And a third option is to use the dotnet build-image global tool that I introduced in the article Containerize .NET applications without writing Dockerfiles.

    Feel free to pick the option you prefer. I'm going to use the third option.

    Install the global tool:

    $ dotnet tool install -g dotnet-build-image

    And now, create the image:

    $ dotnet build-image -t awesome-worker-app:latest -b ubi

    We're passing two arguments. awesome-worker-app:latest is the name of the image. ubi indicates that creation should be based on Red Hat's UBI images. You can pick another base, such as alpine or jammy-chiseled.

    For this tutorial, we'll deploy the systemd unit on the local system. To deploy it on several systems, you can set the tag to match the image registry (for example: -t quay.io/tmds/awesome-worker-app:latest), and add the --push argument to push the image to the registry after it was built.

    The following command runs the image locally:

    $ podman run –rm awesome-worker-app:latest

    As this executes, you'll see the messages from the containerized .NET application, similar to when executing dotnet run. Terminate the container by pressing Ctrl+C.

    Creating a systemd unit

    We'll use Podman to generate a systemd unit file for us.

    Start the container by running the following command:

    $ podman run --name awesome-worker-app --rm --label "io.containers.autoupdate=local" localhost/awesome-worker-app:latest

    This command is similar to the one we used earlier. We're using the fully qualified name of the image (by adding the localhost/ prefix), and we've added a label (io.containers.autoupdate), which we'll discuss in a moment. If your image is deployed to a remote registry, set the label value to registry instead of local.

    If your application uses .NET 7, which supports systemd containers, you can add --sdnotify=container to use that support.

    In another terminal, run:

    $ podman generate systemd --new -n -f --start-timeout 600 awesome-worker-app

    This will write out a file named container-awesome-worker-app.service with the following content:

    # container-awesome-worker-app.service
    # autogenerated by Podman 4.2.0
    
    [Unit]
    Description=Podman container-awesome-worker-app.service
    Documentation=man:podman-generate-systemd(1)
    Wants=network-online.target
    After=network-online.target
    RequiresMountsFor=%t/containers
    
    [Service]
    Environment=PODMAN_SYSTEMD_UNIT=%n
    Restart=on-failure
    TimeoutStartSec=600
    TimeoutStopSec=70
    ExecStartPre=/bin/rm -f %t/%n.ctr-id
    ExecStart=/usr/bin/podman run \
        --cidfile=%t/%n.ctr-id \
        --cgroups=no-conmon \
        --rm \
        --sdnotify=conmon \
        -d \
        --replace \
        --name awesome-worker-app \
        --label io.containers.autoupdate=local localhost/awesome-worker-app:latest
    ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id
    ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id
    Type=notify
    NotifyAccess=all
    
    [Install]
    WantedBy=default.target

    This systemd file isn't specific to your system. It can be used to deploy the image anywhere.

    We've provided a large --start-timeout value of 10 minutes to allow the system time to pull the image (in case the image is hosted on a remote registry).

    The label we added through the --label argument shows up in the ExecStart command. This label is detected by a podman auto-update command, which monitors all containers with this label. If a new image is available, the command pulls it and restarts the systemd unit.

    Podman ships with a timer that triggers the update daily at midnight. If the new image fails to deploy, Podman even rolls back the container to the previous version. Failure detection works best if you use the --sdnotify=container option (with .NET 7).

    Now, if you host the image in an image registry solely using this unit file, you can deploy the .NET application to a large number of systems and even have it update automatically.

    Deploying the service

    You can deploy this unit with the system units at /usr/systemd/system. We'll deploy it as part of our user's systemd services as follows:

    $ mkdir -p ~/.config/systemd/user
    $ cp container-awesome-worker-app.service ~/.config/systemd/user/awesome-worker-app.service
    $ systemctl --user daemon-reload
    $ systemctl --user start awesome-worker-app

    That's it—your service is up and running.

    You can get the logs of the service by running the following:

    $ journalctl --user -f -u awesome-worker-app

    If you'd like this service to start at boot, you have to enable lingering for your user and enable the service as follows:

    $ sudo loginctl enable-linger $(id -u)
    $ systemctl -- user start awesome-worker-app

    Triggering an update

    To finish the example, let's see auto-update in action.

    First, change the message that is printed in Worker.cs:

      {
        while (!stoppingToken.IsCancellationRequested)
        {
    -     _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
    +     _logger.LogInformation("Updated worker running at: {time}", DateTimeOffset.Now);
          await Task.Delay(1000, stoppingToken);
        }
      }

    Then rebuild the image:

    $ dotnet build-image -t awesome-worker-app:latest -b ubi

    The new image is now available. A system that has the auto-update timer enabled will start using it at midnight. But you don't have to wait for that: to update the image the right way, just invoke podman auto-update:

    $ podman auto-update
    UNIT                        CONTAINER                          IMAGE                      POLICY  UPDATED
    awesome-worker-app.service  342942f00932 (awesome-worker-app)  awesome-worker-app:latest  local   true

    The command updates the service, and you can see the change in the journal:

    $ journalctl --user -f -u awesome-worker-app

    Containers and new .NET features simplify systemd

    This article has covered how to deploy .NET applications as systemd services using containers. Containers offer a convenient way to distribute, deploy, and update your application. You've also learned that applications using .NET 7 support notifying systemd about their readiness when running in containers.

    Last updated: September 19, 2023

    Related Posts

    • Running systemd in a non-privileged container

    • Managing temporary files with systemd-tmpfiles on RHEL 7

    • Monitoring .NET Core applications on Kubernetes

    • Containerize .NET for Red Hat OpenShift: Linux containers and .NET Core

    Recent Posts

    • How to run a fraud detection AI model on RHEL CVMs

    • How we use software provenance at Red Hat

    • Alternatives to creating bootc images from scratch

    • How to update OpenStack Services on OpenShift

    • How to integrate vLLM inference into your macOS and iOS apps

    What’s up next?

    Ready to level up your Linux knowledge? Our Intermediate Linux Cheat Sheet presents a collection of Linux commands and executables for developers and system administrators who want to move beyond the basics.

    Get the cheat sheet
    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Products

    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform

    Build

    • Developer Sandbox
    • Developer Tools
    • Interactive Tutorials
    • API Catalog

    Quicklinks

    • Learning Resources
    • E-books
    • Cheat Sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site Status Dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Report a website issue