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:
- How to make applications for Linux using .NET.
- 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:
- Loaded the data from a file called "data.txt" and read it line by line.
- Replaced all the extra whitespaces with just one single whitespace to make splitting easier.
- 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
- Created a touched list to keep track of the latest images. We avoided the first occurrence and took the rest to the _image list.
- 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.