How I Created A Docker Utility For Linux With C# And .NET 6

Writing C# Applications For Linux

Posted by Bishal Sarker on 22/12/2024

C# is a very powerful language and .NET Framework is powerful tool. It's vast and you can create many things with it. Microsoft is continuously updating everyday it's development tools to make development faster and smoother. However, when these powerful tools meet Linux, things even get crazier. In this article I'll be focused on two things here:

  1. How to make applications for Linux using .NET.
  2. How you can leverage your programming skills by solving your own problems.


Before moving towards the implementation let me describe what was the motivation behind creating this tool:

All our applications run Ubuntu 20.04 Server and it's deployed through CI/CD and Docker. So every time we are pushing our commit our pipeline is gonna create a new image and push it to the AWS ECR (Elastic Container Registry) and run that in a container in our server. We are using a named container so on every deployment we are replacing the container with new updates (deleting and creating new container with the same name). But the images are new every time and they growing with each deployment:


ubuntu@ip-0-0-0-0:~$ sudo docker images

REPOSITORY                                                    TAG            IMAGE ID       CREATED             SIZE
000000000000.dkr.ecr.region.amazonaws.com/application-image   519            fe2f29c83f26   13 minutes ago      473MB
000000000000.dkr.ecr.region.amazonaws.com/application-image   519            cfcc6335be2a   15 minutes ago      474MB
000000000000.dkr.ecr.region.amazonaws.com/application-image-2 517            34268b967fd5   About an hour ago   473MB
000000000000.dkr.ecr.region.amazonaws.com/application-image-2 517            5fe5abc1b3cc   About an hour ago   474MB
000000000000.dkr.ecr.region.amazonaws.com/application-image   515            401e9f50e89d   3 hours ago         473MB
000000000000.dkr.ecr.region.amazonaws.com/application-image   515            cf5f8393625a   3 hours ago         474MB
000000000000.dkr.ecr.region.amazonaws.com/application-image-2 656            17e248ce07fc   3 hours ago         1.52GB


That's the issue. It's taking our machine space and it's bad. So what we were doing is deleting images with the image id accept the updated one which is currently being used in a running container. That was pretty time consuming and annoying. We needed something automated. We needed a tool. So what I did is exactly what I was doing manually but with code.


Docker Command Maker

It's a basic C# console application which parse the docker images table, extract the latest docker image id creates remove commands. So let's do the parsing task first:

public class CommandGenerator
    {
        private readonly List _images;
        private readonly List _ignoredRepo;


        public Maker()
        {
            _images = new List();
            _ignoredRepo = new List();


            var data = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "data.txt"))
                .ToList()
                .Select(x => Regex.Replace(x, @"\s+", " "))
                .ToList();


            _ignoredRepo.AddRange(File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ignore.txt")).ToList());


            if (data.Any()) data.RemoveAt(0);


            var touched = new List();
            foreach(var lines in data)
            {
                var splits = lines.Split(' ');
                var repo = splits[0].Trim();


                if (!_ignoredRepo.Any(x => x.Trim() == repo))
                {
                    if (touched.Any(x => x.Trim() == repo))
                    {
                        _images.Add(splits[2]);
                    }


                    touched.Add(repo);
                }
            }
        }


        public void GenerateDockerImageRemoveCommand()
        {
            foreach(var image in _images)
            {
                Console.WriteLine($"sudo docker image rm {image}");
            } 
        }
    }

What I did is:

  1. Loaded the data from a file called "data.txt" and read it line by line.
  2. Replaced all the extra whitespaces with just one single whitespace to make splitting easier.
  3. I have also added an ignored list (ignore.txt) to ignore certain images which I don't want to clean up. I have loaded the data and read it line by line: For example, my ignore list is:
    rabbitmq
    redis
    dpage/pgadmin4
    postgres
    datalust/seq
    hello-world

Docker image data is already sorted by latest date. So what we have to do is to take the first string occurrence and add the rest to the _image list to remove. So, I

  1. Created a touched list to keep track of the latest images. We avoided the first occurrence and took the rest to the _image list.
  2. Before adding it to the list I also checked if it is in the ignore list or not.

Well, our parsing is done now we have to trigger it to create the actual commands.

using DockerCommandMaker;

internal class Program
{
    private static void Main(string[] args)
    {
        var maker = new CommandGenerator();
        maker.GenerateDockerImageRemoveCommand();
    }
}

GenerateDockerImageRemoveCommand() will create and print out the docker commands.



Now we gonna need to publish the application and port it for the Linux. It's easy, just create a publish profile for the Linux. Make it Self-Contained so that all the dependencies are merged inside one single file:


Okay, after publishing it we need to create to bash files:

  • dcm.sh: Where we are going to get the docker images and store that result in "data.txt".
sudo rm data.txt
sudo rm rmcmnds.sh
sudo docker images >> data.txt
chmod 777 ./DockerCommandMaker
./DockerCommandMaker >> rmcmnds.sh
sudo sh rmcmnds.sh
  • rmcmnds.sh: This is where we are going to store the image remove commands and finally run those commands.


We are done! The final task is to run it:

sudo sh dcm.sh


Let me know about your thoughts.