<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Raphael Habereder</title>
    <description>The latest articles on DEV Community by Raphael Habereder (@habereder).</description>
    <link>https://dev.to/habereder</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F8885%2Fd2b1e36e-bd2c-4101-99c8-8a9c1b80aa9d.jpeg</url>
      <title>DEV Community: Raphael Habereder</title>
      <link>https://dev.to/habereder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/habereder"/>
    <language>en</language>
    <item>
      <title>My first real frontend app in 13 years, and it's actually just a joke</title>
      <dc:creator>Raphael Habereder</dc:creator>
      <pubDate>Sat, 22 Aug 2020 18:11:58 +0000</pubDate>
      <link>https://dev.to/habereder/my-first-real-frontend-app-and-it-s-actually-just-a-dumb-joke-3868</link>
      <guid>https://dev.to/habereder/my-first-real-frontend-app-and-it-s-actually-just-a-dumb-joke-3868</guid>
      <description>&lt;h1&gt;
  
  
  The problem of having a too big ego
&lt;/h1&gt;

&lt;p&gt;So today I had a discussion with a girl friend of mine. &lt;br&gt;
She watched a Netflix show wherein there was a little "mood webapp". &lt;/p&gt;

&lt;p&gt;The boyfriend would go on this website and would instantly see the mood of his girlfriend. &lt;/p&gt;

&lt;p&gt;This is the reference-material I had:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxxfj27wfvkvpkyyaxv9d.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxxfj27wfvkvpkyyaxv9d.PNG" alt="Reference Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My friend told me about it and asked with huge adorable puppy eyes "can you make something like that too?". Looking at the screenshot and miserably failing to keep my big ego in check, I, obviously, said "Of course I can!". &lt;/p&gt;
&lt;h1&gt;
  
  
  The Problem
&lt;/h1&gt;

&lt;p&gt;As you can see in the screenshot, in the show the girlfriend programmed this app in Rust and Webassembly, but screw that!&lt;/p&gt;
&lt;h2&gt;
  
  
  My secret
&lt;/h2&gt;

&lt;p&gt;Now, here's the deal. Just between you and me, I might be confident enough to proudly call myself a DevOps, but the Dev part of that never had anything to do with Frontends. I'm more the service-guy/automation dude. &lt;br&gt;
Looking at my portfolio of languages I mastered, there is nothing really web-centric, so I would be starting at 0, regardless of what I could have chosen.  &lt;/p&gt;

&lt;p&gt;This is what I am comfortable programming in: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;C#&lt;/li&gt;
&lt;li&gt;A tad of Python (ansible)&lt;/li&gt;
&lt;li&gt;A little bit of Ruby (Chef/Puppet)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The realization
&lt;/h2&gt;

&lt;p&gt;I suck at Javascript and CSS. Full-Stop. My knowledge is brutally outdated, I slacked off in the Frontend department. &lt;br&gt;
The last time I had anything to do with JavaScript and CSS was in the beginning of JQuery about 2007 or so. &lt;br&gt;
That's a very long time for anything IT and especially the ever faster evolving JS-World. &lt;/p&gt;
&lt;h1&gt;
  
  
  Onwards to (hacky) glory
&lt;/h1&gt;

&lt;p&gt;I thought, "to hell with it, I'll try one of these fancy new frameworks" and settled on Vue.js. No idea why, maybe because the name is written funny and I remembered it because of that. I even learned that it's pronounced "view", not "wu", so grant me bonus points for that please. I will need them to balance my hacky stuff later. &lt;/p&gt;
&lt;h2&gt;
  
  
  The Stuff
&lt;/h2&gt;

&lt;p&gt;So what is it I used?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vue.js with Vuetify&lt;/li&gt;
&lt;li&gt;Express&lt;/li&gt;
&lt;li&gt;Tons of dependencies, cause I have no idea what I'm doing and I'm going in the deep end&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What is missing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some persistent storage (maybe a cassandra or ignite for some real nice overkill)&lt;/li&gt;
&lt;li&gt;Docker &lt;/li&gt;
&lt;li&gt;Kubernetes &lt;/li&gt;
&lt;li&gt;Some kind of easy to use admin-interface for my friend Laura&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And yes, I absolutely will shoehorn Docker and Kubernetes in somehow, I actually need to use something I feel comfortable with. &lt;br&gt;
Including this post, this project took me 6 hours, which feels horribly slow to be honest.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo Time!
&lt;/h2&gt;

&lt;p&gt;So how does my version look?&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fynyouwwky4ro2mf1qu44.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fynyouwwky4ro2mf1qu44.PNG" alt="Mood Meter Demo without Banner"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If they feel like it, they can also add a Banner-Message that jumps into your face, which is only rendered when set&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzjx74907t2sv5035llmh.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzjx74907t2sv5035llmh.PNG" alt="Mood Meter Demo with Banner"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If this doesn't get your immediate attention, I don't know what will. &lt;/p&gt;
&lt;h3&gt;
  
  
  What is that abomination??
&lt;/h3&gt;

&lt;p&gt;To get this done as close to the reference material as possible, I needed some kind of ready to use component that included a bar and something to move along that bar. Since I had no idea how to do it myself, I thought "Hey! I could wrestle a slider into submission for this!"&lt;br&gt;
So I used a vuetify v-slider and just styled the hell out of it so it looks how I wanted it. &lt;br&gt;
What you can't really see, is that the picture is actually animated to move indefinitely up and down along the bar. I was told it did that in the show too, so I had to copy it. &lt;/p&gt;

&lt;p&gt;Behold the abomination I made of the slider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;.v-slider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;89&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="no"&gt;lightgreen&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;border-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;-webkit-box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;40px&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;40px&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;.v-slider__thumb&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;350px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;-webkit-animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mover&lt;/span&gt; &lt;span class="m"&gt;2.5s&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;  &lt;span class="n"&gt;alternate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mover&lt;/span&gt; &lt;span class="m"&gt;2.5s&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;  &lt;span class="n"&gt;alternate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url('../assets/laui.png')&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;.v-slider--horizontal&lt;/span&gt; &lt;span class="nc"&gt;.v-slider__track-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@-webkit-keyframes&lt;/span&gt; &lt;span class="n"&gt;mover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-190px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;mover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-190px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since I don't really know what I am doing, this might be very very bad. But it works, so I am fine with it for the moment. &lt;br&gt;
Another side-effect is, that the actual slider bubble thing is enormous with 300x350px. This somehow results in the picture going way off the bar at the min-value 0 and max-value of 100. I need to tweak that a little more. So 10-90 as values have to suffice until I find out why the bloody thing does that.&lt;/p&gt;

&lt;p&gt;As of now, the whole "page" consists of one Vue-Component, a &lt;em&gt;Lauimeter&lt;/em&gt;, which was named so by my friend. &lt;/p&gt;

&lt;p&gt;The component gets all it's data, including labels and texts, from an express-service with the following endpoints: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;get("/") to just get everything stored&lt;/li&gt;
&lt;li&gt;post("/moodUpdate") to just update the mood value&lt;/li&gt;
&lt;li&gt;post("/bannerUpdate") to update the bannermessage&lt;/li&gt;
&lt;li&gt;post('/config') to configure the service for testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To configure the app, a simple curl is enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl localhost:3000/config &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{ "mood": 90, "minMessage": "Not today", "maxMessage":"Fantastic!", "updateText":"Update", "meterName":"Moodmeter", "bannerMessage": "" }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I have to figure out how to get a "easy to use config-page" in there, so my friend can actually use the bloody thing without having to learn cURL. And a persistent storage would be nice. &lt;/p&gt;

&lt;p&gt;Maybe this stupid little fun project will find it's way to github too one day. But that is something for another day. As the first javascript experiment in 13 years, I am absolutely okay with how hacky this turned out. &lt;/p&gt;

&lt;p&gt;It was actually a lot of fun, I can see myself doing more of this. But hopefully better in the future. &lt;/p&gt;

&lt;p&gt;Feel free to leave feedback. All this is very new to me, so any criticism and/or guidance is most welcome. Maybe old dogs &lt;em&gt;can&lt;/em&gt; learn new tricks.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>vue</category>
    </item>
    <item>
      <title>When and why do you use go?</title>
      <dc:creator>Raphael Habereder</dc:creator>
      <pubDate>Wed, 05 Aug 2020 07:31:12 +0000</pubDate>
      <link>https://dev.to/habereder/when-and-why-do-you-use-go-4ha</link>
      <guid>https://dev.to/habereder/when-and-why-do-you-use-go-4ha</guid>
      <description>&lt;p&gt;I really like go, &lt;em&gt;as a replacement to python for system automation and admin tasks&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;In the past I tried building client-software using &lt;a href="https://github.com/fyne-io/fyne"&gt;fyne-io/fyne&lt;/a&gt;, which was fun, but it was not as feature-rich as I hoped. &lt;/p&gt;

&lt;p&gt;I haven't gone beyond the simple rest-service, or automation for quite a while. That's where I always look back at python. &lt;/p&gt;

&lt;p&gt;What is your opinion on go and it's use-cases? &lt;/p&gt;

</description>
      <category>discuss</category>
      <category>go</category>
    </item>
    <item>
      <title>A rant about discussion culture on the internet</title>
      <dc:creator>Raphael Habereder</dc:creator>
      <pubDate>Sat, 01 Aug 2020 10:51:25 +0000</pubDate>
      <link>https://dev.to/habereder/a-rant-about-discussion-culture-in-the-internet-39d5</link>
      <guid>https://dev.to/habereder/a-rant-about-discussion-culture-in-the-internet-39d5</guid>
      <description>&lt;p&gt;Today was the first time I found a comment of mine flagged as "not constructive by the community". &lt;br&gt;
This irked me a little bit, since I see myself as rather inclusive, yet strong-minded and fair. &lt;/p&gt;

&lt;p&gt;Don't worry, this post will go a little bit deeper into the topic, just bear with me for a second. &lt;/p&gt;

&lt;p&gt;Now I'm quite sure a user has no way to "negatively impact"  someone else on here, except for submitting a hard report. &lt;/p&gt;

&lt;p&gt;So here is the question, where does this come from?&lt;br&gt;
It says it right there on the tin, "by the community", even if there is no voting system on comments. &lt;/p&gt;

&lt;p&gt;So it must have been a report, right? Let's follow this chain a little deeper. &lt;/p&gt;

&lt;p&gt;If you want to submit a report, you'll get a handful of categories to describe the issue: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rude or vulgar&lt;/li&gt;
&lt;li&gt;Harassment or hate speech&lt;/li&gt;
&lt;li&gt;Spam or copyright issue&lt;/li&gt;
&lt;li&gt;Inappropriate listings message/category&lt;/li&gt;
&lt;li&gt;Other&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This seems like a solid selection, except for "rude or vulgar" and the obvious "other". &lt;br&gt;
Rude is an attribute that is solely subjective to the receiver. &lt;br&gt;
Speaking from experience, being a &lt;em&gt;typical franconian german&lt;/em&gt;, being direct without any sugarcoating is more often than not perceived by people as being rude. &lt;/p&gt;

&lt;p&gt;Personally, I think it's quite flattering if someone tells me straight how it is. If you don't like something, say it. Beating around the bush gives leeway to misinterpretation. Misinterpretation again escalates situations even further. &lt;/p&gt;

&lt;p&gt;So this, in my humble opinion, is a problem.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Other&lt;/strong&gt; category seems to be just another catchall for &lt;em&gt;We are not sure where it fits, but want to reserve judgement&lt;/em&gt;, which is perfectly fine.&lt;/p&gt;

&lt;p&gt;That out of the way, let's get into the topic of the title. &lt;/p&gt;

&lt;p&gt;I've been on DEV for a bit over 3 years now, and most of the time I've been quiet. &lt;br&gt;
I read a lot of controversial topics on here, especially when the US launches yet another political outrage party. Let's be honest, something that happens in the US is found here or on other social media platforms shortly after. The US is just that much of a powerhouse in global communication. &lt;/p&gt;

&lt;p&gt;What I have noticed is, the discussions here, and the rest of the internet, are becoming more and more of an echo-chamber, rather than actual discussions.&lt;br&gt;
The level on DEV is still far above other platforms, let's be real for a second, but the discussion culture of DEV is slowly getting there. &lt;/p&gt;

&lt;p&gt;I read the code of conduct, and with most parts I agree. It's solidly written, even though I am quite sure back in 2017 it was more compact than it is today. &lt;br&gt;
The trouble I have is, I can't find a way to "engage someone" in a debate with what the &lt;a href="https://dev.to/code-of-conduct"&gt;COC&lt;/a&gt; deems as "safe".&lt;/p&gt;

&lt;p&gt;Two points strike me as particularly deadly for discussion culture: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Examples of unacceptable behavior by participants include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dismissing or attacking inclusion-oriented requests&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;A discussion is just that. You discuss points and if you  argue against a differing view point, it is, by design, an attack on someones opinion. This, in no way, means you should actually insult someone. But an objective, solid argument, or let's go with the times and pardon my language, you &lt;em&gt;call someones bullshit in question&lt;/em&gt;, that is an attack. &lt;/p&gt;

&lt;p&gt;I want to be clear, I am absolutely in favor of including people that have been excluded for way too long. But not everything that is marketed as "inclusive" is done for the excluded. PR and virtue-signalling are a thing, they have been for a long time, and they work. &lt;/p&gt;

&lt;p&gt;The other point that strikes me as terribad for discussions is this one: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We pledge to prioritize marginalized people’s safety over privileged people’s comfort. We will not act on complaints regarding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Someone’s refusal to explain or debate social justice concepts&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a prime example of echo-chambers. You say something that is currently controversial, either because you beliefve in it, or because you want to spark a flame-war (lets be honest, who doesn't love a good flamewar?) &lt;br&gt;
Someone engages your argument, you submit a report and classify it as harassment because you "refuse to explain or debate social justice concepts".&lt;/p&gt;

&lt;p&gt;And the COC supports that. Why? &lt;br&gt;
If you don't want to discuss something, don't post it on the internet. There is always someone that disagrees, the typical "am I the only one?" question is almost always answered by a simple "thats a 1 in seven dot eight billion chance, so probably not". &lt;/p&gt;

&lt;p&gt;It seems to me, that a lot of people jump on bandwagons here, freely repeating along half-truths they read a few posts back, further strengthening the echo. Some actually debate these things because they believe in them, which is exactly what should happen. &lt;/p&gt;

&lt;p&gt;Others on the other hand, just repeat controversial things to farm some heart-emojis. &lt;br&gt;
While this is one of the more typical behaviors of a social-platform with any kind of rating-mechanism, what DEV and many other platforms are missing is the counter weight. &lt;br&gt;
A solid mechanism to downvote things and equalize echos. &lt;/p&gt;

&lt;p&gt;Of course, you may say &lt;br&gt;
&lt;em&gt;this would lead to brigading, vote-flooding, or vote-bombing, which should be held in check!&lt;/em&gt;&lt;br&gt;
I agree, but you don't achieve this by nuking everything you don't agree with, that itself is going against the very thing you want to protect: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;fostering an open and welcoming environment&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's go down the rabbit hole further. By stripping out the feature to express your disagreement safely without being punished, you get the exact same rapid momentum in the opposite direction. &lt;br&gt;
There can be no equilibrium, if a rating/voting mechanism goes only one way. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;But you can write comments to express your differing view point!&lt;/em&gt;, is what you might think now. Absolutely, I agree, but that loops us back to the beginning, being "marked as not constructive by the community".&lt;/p&gt;

&lt;p&gt;What I learned is, that "inclusion" is a paradox. &lt;br&gt;
That if you want to be inclusive, and heavily enforce this, you almost always exclude a huge chunk of people. Thereby inclusion is inherently exclusive. &lt;/p&gt;

&lt;p&gt;So where does this post lead us? I don't know, it's just the rant of a dude that doesn't understand the discussion culture of the internet. A guy that is direct, and not afraid to step on your foot if you spout half-truths. A guy that gets marked as not-constructive and follows up with writing a gigantic tirade :D &lt;/p&gt;

&lt;p&gt;Do I have a solution? No, not really. I would be rich if I had one on hand. All I want is a "dislike" button, but that's a thing I won't get in today's internet. I'll just leave comments and hope I'll get more cool discussions out of it :)&lt;/p&gt;

&lt;p&gt;Feel free to chime in, and please, don't just echo me, hit me with the good stuff. I actually want discussions to embiggen the "apparently not yet big picture" I see. &lt;/p&gt;

&lt;p&gt;Rock on and have a great weekend, I hope your weather is just as lovely as in my part of this beautiful rock! &lt;/p&gt;

</description>
      <category>healthydebate</category>
    </item>
    <item>
      <title>Continuous Test-Reporting with Allure</title>
      <dc:creator>Raphael Habereder</dc:creator>
      <pubDate>Fri, 26 Jun 2020 19:35:57 +0000</pubDate>
      <link>https://dev.to/habereder/continuous-test-reporting-with-allure-1ag4</link>
      <guid>https://dev.to/habereder/continuous-test-reporting-with-allure-1ag4</guid>
      <description>&lt;h1&gt;
  
  
  The disconnect between Tech and Client
&lt;/h1&gt;

&lt;p&gt;I had a talk with one of my clients which went like this:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tekton and Kubernetes are nice and all, our devs seem to be quite happy, but where do I see Test results? Jenkins has a feature like this, but now all developers use this tekton stuff and all these fancy console commands. &lt;br&gt;
I just want to look at my reports and get a glimpse of how the softwaretests went. Those terminals don't really do that for me. &lt;br&gt;
Can you get me something like that?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's a spot-on observation by my client. A lot of Kubernetes tools are heavily focused on CLI-Usage, or in the "early stages of development", when features are higher up on the priority-list than shiny UIs. &lt;/p&gt;

&lt;p&gt;One of these examples would be the tekton ecosystem. There are rarely any UIs, and if there are, like the Tekton Dashboard for example, they are very barebones. &lt;/p&gt;

&lt;p&gt;Since I am a very CLI-Centric guy and by habit don't really think about using UIs that often, the request of my client ran me over like a truck. &lt;/p&gt;

&lt;p&gt;So we dug around a bit and found a very promising tool. &lt;br&gt;
The &lt;a href="http://allure.qatools.ru/" rel="noopener noreferrer"&gt;Allure Framework&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is allure and why should I use it?
&lt;/h2&gt;

&lt;p&gt;The allure framework does a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generating Results in json-Format (thank god no more xml)&lt;/li&gt;
&lt;li&gt;Report creation&lt;/li&gt;
&lt;li&gt;History-Data Collection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this is then slapped together in a pretty static-html report, ready to be served on your favorite server. &lt;br&gt;
If you don't have one on hand, the optionally downloadable cli can also spin up a Webserver via the &lt;code&gt;allure serve pathToReportDir&lt;/code&gt; command and host the reports itself. Magical!&lt;/p&gt;

&lt;p&gt;To give you a little tour of how a report looks, have a gif!&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbkhsej9r5ez27cgjk1my.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbkhsej9r5ez27cgjk1my.gif" alt="Allure Report Demo Gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I tried to get a gif to work, but 80% of the time I get errors from dev, and the ones that render turn into potato-quality. &lt;br&gt;
No idea what this cloudinary thing does, but it's not great..&lt;/p&gt;

&lt;p&gt;So if this is too small/ugly for you, which I can probably only blame on myself, don't worry! I'll show you how to spin up a demo yourself :) &lt;/p&gt;

&lt;h2&gt;
  
  
  How does it work?
&lt;/h2&gt;

&lt;p&gt;First off, Allure has tons of connectors for different frameworks and languages, which are documented &lt;a href="https://docs.qameta.io/allure/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But let me warn you: The documentation seems to be very minimal right now. There is a lot of trial and error involved to get what you want&lt;/em&gt;&lt;br&gt;
To be fair, it seems to be a very powerful tool with many supported languages and Frameworks. Documenting everything to the smallest detail would probably be an enormous task.&lt;/p&gt;

&lt;p&gt;My client was especially interested in the Cucumber and JUnit5 support. As of now we roughly finished implementing a continous integration pipeline with Report generation for JUnit5 Testsuites. Cucumber is probably following as soon as I find the time. &lt;/p&gt;

&lt;h3&gt;
  
  
  Let's test stuff!
&lt;/h3&gt;

&lt;p&gt;To get allure to generate a report for you, all you need is the following additions to your pom.xml: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;properties&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;surefire-plugin.version&amp;gt;&lt;/span&gt;2.22.2&lt;span class="nt"&gt;&amp;lt;/surefire-plugin.version&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;allure.version&amp;gt;&lt;/span&gt;2.13.3&lt;span class="nt"&gt;&amp;lt;/allure.version&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;aspectj.version&amp;gt;&lt;/span&gt;1.9.5&lt;span class="nt"&gt;&amp;lt;/aspectj.version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/properties&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.qameta.allure&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;allure-junit5&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${allure.version}&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;test&lt;span class="nt"&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;build&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;plugins&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-surefire-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${surefire-plugin.version}&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;testFailureIgnore&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/testFailureIgnore&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;argLine&amp;gt;&lt;/span&gt;
          -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
        &lt;span class="nt"&gt;&amp;lt;/argLine&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;systemPropertyVariables&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;allure.results.directory&amp;gt;&lt;/span&gt;${project.build.directory}/allure-results&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;/allure.results.directory
          &lt;span class="nt"&gt;&amp;lt;java.util.logging.manager&amp;gt;&lt;/span&gt;org.jboss.logmanager.LogManager&lt;span class="nt"&gt;&amp;lt;/java.util.logging.manager&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/systemPropertyVariables&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.aspectj&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;aspectjweaver&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${aspectj.version}&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.qameta.allure&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;allure-maven&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.10.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;phase&amp;gt;&lt;/span&gt;package&lt;span class="nt"&gt;&amp;lt;/phase&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;report&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;reportVersion&amp;gt;&lt;/span&gt;${allure.version}&lt;span class="nt"&gt;&amp;lt;/reportVersion&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;allureDownloadUrl&amp;gt;&lt;/span&gt;https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline/${allure.version}/allure-commandline-${allure.version}.zip&lt;span class="nt"&gt;&amp;lt;/allureDownloadUrl&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;    
        &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/plugins&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/build&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Allure also brings its own set of annotations for Testcases.&lt;br&gt;
Here are a few I like to use: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="nd"&gt;@Feature&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Some Name"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Epic&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Some Name"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Story&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Some Name"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Severity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SeverityLevel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CRITICAL&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"non-dev readable description"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Link&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="no"&gt;JIRA&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Tickets&lt;/span&gt; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="n"&gt;some&lt;/span&gt; &lt;span class="n"&gt;cool&lt;/span&gt; &lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Want to give it a go?&lt;br&gt;
Here is a simple Dockerfile I slapped together, that needs minimal work from your side. &lt;br&gt;
Just create a simple maven project, configure your pom.xml like I've shown above, add some tests, annotate them and give it a run for it's money with a mvn package!&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;maven:3.6.3-jdk-11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /opt/service&lt;/span&gt;

&lt;span class="c"&gt;# This is for caching purposes in local builds&lt;/span&gt;
&lt;span class="c"&gt;# That way you don't have to download the whole internet every time your source changes&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; pom.xml . &lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;mvn dependency:go-offline

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src/ src/&lt;/span&gt;

&lt;span class="c"&gt;# Run the Build and generate a report&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;mvn clean &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mvn allure:report


&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;nginx:1.19.0-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;server&lt;/span&gt;

&lt;span class="c"&gt;# Copy the report and serve it via nginx&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /opt/service/target/site/allure-maven-plugin/ /usr/share/nginx/html/&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  The challenge of CIs
&lt;/h2&gt;

&lt;p&gt;Now we have a nice tool, but what now? &lt;br&gt;
I quickly noticed that those reports may be nice, but if I am going to deploy these reports in a container, I need a little bit more than what I showed you earlier. &lt;br&gt;
Allures history-feature can't do anything this way. Allure can't create a history if there is no data of previous runs to be found. &lt;/p&gt;

&lt;p&gt;So let's create a solution for this! &lt;/p&gt;

&lt;p&gt;I thought I had a nifty idea by designing the report generation process like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgsy7t14nyuw6qe0pbl7d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgsy7t14nyuw6qe0pbl7d.png" alt="Allure Report Process"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, I introduced a new component. MinIO as S3 Storage Provider. Why minIO? Because it's commandline client has a great &lt;em&gt;--newer-than/--older-than Feature&lt;/em&gt;. So minio could do the sorting work for me, I don't have to do that myself! &lt;/p&gt;

&lt;p&gt;If I wanted to create a report with Data up to 7 Days old, it would be as easy as &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

mc cp --recursive --newer-than 7d minio/allure/results/ results/
allure generate results/ -o report/
mc cp --recursive report/ minio/allure/reports-history/$(date +%y-%m-%d-%H%M)/ 


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And I'd get a report with Data for 7 Days. It would get stored in minio and would be ready to be put into a container. &lt;br&gt;
Honestly, I loved that idea and was kind of proud of that solution. If you have a better idea, I am all ears!&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's containerize it!
&lt;/h2&gt;

&lt;p&gt;So how would this look inside a container?&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;maven:3.6.3-jdk-11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /opt/service&lt;/span&gt;

&lt;span class="c"&gt;# This is for caching purposes in local builds&lt;/span&gt;
&lt;span class="c"&gt;# That way you don't have to download the whole internet every time your source changes&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; pom.xml . &lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;mvn dependency:go-offline

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src/ src/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;mvn clean &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mvn allure:report

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;openjdk:14-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;generator&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /tmp&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /opt/service/target/allure-results/* results/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /opt/service/target/site/allure-maven-plugin/* reports/&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apk add curl &lt;span class="nb"&gt;tar gzip&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://bintray.com/qameta/maven/download_file?file_path&lt;span class="o"&gt;=&lt;/span&gt;io%2Fqameta%2Fallure%2Fallure-commandline%2F2.13.4%2Fallure-commandline-2.13.4.tgz | &lt;span class="nb"&gt;tar &lt;/span&gt;xvz &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; curl &lt;span class="nt"&gt;-LO&lt;/span&gt; https://dl.min.io/client/mc/release/linux-amd64/mc &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chmod&lt;/span&gt; +x mc &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;$PWD&lt;/span&gt;/mc /usr/bin/mc &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mc config host add minio http://ipofminio:9000 minioadmin minioadmin &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mc mb &lt;span class="nt"&gt;-p&lt;/span&gt; minio/allure &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mc &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt; results/&lt;span class="k"&gt;*&lt;/span&gt; minio/allure/results/ &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mc &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt; reports/&lt;span class="k"&gt;*&lt;/span&gt; minio/allure/reports/&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +&lt;span class="s1"&gt;'%Y-%m-%d-%H%M'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/ &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mc &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt; minio/allure/results/ results/  &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; allure-2.13.4/bin/allure generate results/ &lt;span class="nt"&gt;-o&lt;/span&gt; reports-with-history-data/ &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mc &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt; reports-with-history-data/ minio/allure/reports-history/&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +&lt;span class="s1"&gt;'%Y-%m-%d-%H%M'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;nginx:1.19.0-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;server&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=generator /tmp/reports-with-history-data/ /usr/share/nginx/html/&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;What we did is, we introduced another stage with &lt;code&gt;FROM openjdk:14-alpine as generator&lt;/code&gt;&lt;br&gt;
in which we do all the upload/fetching/sorting stuff for our history-reports. &lt;br&gt;
All you would need to build this is a minio instance, which is, for local testing, easily deployed via:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

docker run -p 9000:9000 --name minio --rm minio/minio server /data


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  But what about tekton and kubernetes?
&lt;/h3&gt;

&lt;p&gt;You may ask this, rightfully, since I teased it in the beginning. &lt;br&gt;
Making this run in tekton was pretty easy, even though it needed a little bit more prep-work.&lt;/p&gt;

&lt;p&gt;First off would be to create an Image, that would be buildable outside of Kubernetes, and still has all the connections to minio we need. &lt;br&gt;
The Dockerfile up there would probably crash the build, since a minio running in kubernetes wouldn't be reachable by your CI, which normally is running outside of kubernetes. So let's modify our Container a little bit to work with those requirements.&lt;/p&gt;

&lt;p&gt;What do we have to do?&lt;br&gt;
First off, I want to split up my container into two. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a reporting container that builds the reports and pushes them to minio&lt;/li&gt;
&lt;li&gt;a nginx container that pulls the report and serves it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why do I want this? &lt;br&gt;
Having to build an image that contains your report every time the report changes, doesn't seem viable to me. It takes unnecessary space in your docker registry and doesn't bring that much benefit. IMO it would be better to have a dynamic container that can be parameterized and does the whole result-pulling, generating and archiving in it's entrypoint. So let's do just that!&lt;/p&gt;
&lt;h3&gt;
  
  
  Allure-Reporter Container
&lt;/h3&gt;

&lt;p&gt;Dockerfile.allure&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; openjdk:14-alpine&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /tmp&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apk add curl &lt;span class="nb"&gt;tar gzip&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://bintray.com/qameta/maven/download_file?file_path&lt;span class="o"&gt;=&lt;/span&gt;io%2Fqameta%2Fallure%2Fallure-commandline%2F2.13.4%2Fallure-commandline-2.13.4.tgz | &lt;span class="nb"&gt;tar &lt;/span&gt;xvz &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; curl &lt;span class="nt"&gt;-LO&lt;/span&gt; https://dl.min.io/client/mc/release/linux-amd64/mc &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chmod&lt;/span&gt; +x mc &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;$PWD&lt;/span&gt;/mc /usr/bin/mc &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;$PWD&lt;/span&gt;/allure-2.13.4/bin/allure /usr/bin/allure &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; allure &lt;span class="nt"&gt;--version&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mc &lt;span class="nt"&gt;--version&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /opt/myproject&lt;/span&gt;
&lt;span class="k"&gt;VOLUME&lt;/span&gt;&lt;span class="s"&gt; /opt/myproject/results&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; entrypoint-allure.sh /usr/local/bin/entrypoint&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/local/bin/entrypoint 

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nobody&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; bash -c entrypoint&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;entrypoint-allure.sh&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="c"&gt;# Capture the name of the project, so we can differentiate results in minio&lt;/span&gt;
&lt;span class="nv"&gt;CURRENT_DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%Y%m%d%H%M'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; 

&lt;span class="c"&gt;# Gather all results you can find&lt;/span&gt;
&lt;span class="c"&gt;# Should be Emulated via Volume Mount locally&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;results
&lt;span class="k"&gt;for &lt;/span&gt;resultDir &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; d &lt;span class="nt"&gt;-iname&lt;/span&gt; allure-results&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nv"&gt;$resultDir&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt; results
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="c"&gt;# Generate the actual allure report of this testrun and publish it&lt;/span&gt;
allure generate results/ &lt;span class="nt"&gt;-o&lt;/span&gt; allure-report/

&lt;span class="c"&gt;# Configure Minio and make sure the bucket allure exists&lt;/span&gt;
mc config host add minio &lt;span class="nv"&gt;$MINIO_ENDPOINT&lt;/span&gt; &lt;span class="nv"&gt;$MINIO_ACCESS_KEY&lt;/span&gt; &lt;span class="nv"&gt;$MINIO_SECRET_KEY&lt;/span&gt; 
mc mb &lt;span class="nt"&gt;-p&lt;/span&gt; minio/allure 

&lt;span class="c"&gt;# Publish the results&lt;/span&gt;
mc &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt; results/ minio/allure/&lt;span class="nv"&gt;$PROJECTNAME&lt;/span&gt;/allure-results/
&lt;span class="c"&gt;# Publish the report&lt;/span&gt;
mc &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt; allure-report/ minio/allure/&lt;span class="nv"&gt;$PROJECTNAME&lt;/span&gt;/allure-report-independent/&lt;span class="nv"&gt;$CURRENT_DATE&lt;/span&gt;/

&lt;span class="c"&gt;# Get all results, including those from past runs, and generate a history report to publish&lt;/span&gt;
mc &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt; minio/allure/&lt;span class="nv"&gt;$PROJECTNAME&lt;/span&gt;/allure-results/ results/ 
allure generate results &lt;span class="nt"&gt;-o&lt;/span&gt; allure-report-history/ 
mc &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt; allure-report-history/ minio/allure/&lt;span class="nv"&gt;$PROJECTNAME&lt;/span&gt;/allure-report-history/&lt;span class="nv"&gt;$CURRENT_DATE&lt;/span&gt;/



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Allure-Server Container
&lt;/h3&gt;

&lt;p&gt;Dockerfile.nginx:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;nginx:1.19.0-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;server&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; MINIO_ENDPOINT=http://minio.default.svc.cluster.local:9000&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; MINIO_SECRET_KEY=minioadmin&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; MINIO_SECRET_PASS=minioadmin&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; PROJECTNAME=myproject&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-LO&lt;/span&gt; https://dl.min.io/client/mc/release/linux-amd64/mc &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chmod&lt;/span&gt; +x mc &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;$PWD&lt;/span&gt;/mc /usr/bin/mc &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;$PWD&lt;/span&gt;/allure-2.13.4/bin/allure /usr/bin/allure &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mc &lt;span class="nt"&gt;--version&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; entrypoint-nginx.sh /usr/local/bin/entrypoint&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/local/bin/entrypoint 

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nobody&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; bash -c entrypoint&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;entrypoint-nginx.sh:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="c"&gt;# Capture the name of the project, so we can differentiate results in minio&lt;/span&gt;
&lt;span class="nv"&gt;CURRENT_DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%Y%m%d%H%M'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; 

&lt;span class="c"&gt;# Configure Minio and make sure the bucket allure exists&lt;/span&gt;
mc config host add minio &lt;span class="nv"&gt;$MINIO_ENDPOINT&lt;/span&gt; &lt;span class="nv"&gt;$MINIO_ACCESS_KEY&lt;/span&gt; &lt;span class="nv"&gt;$MINIO_SECRET_KEY&lt;/span&gt;
mc mb &lt;span class="nt"&gt;-p&lt;/span&gt; minio/allure 

&lt;span class="nv"&gt;NEWEST_REPORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;mc find minio/allure/&lt;span class="nv"&gt;$PROJECTNAME&lt;/span&gt;/allure-report-history/ &lt;span class="nt"&gt;--maxdepth&lt;/span&gt; 1 &lt;span class="nt"&gt;--newer-than&lt;/span&gt; 1d | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt; &lt;span class="nt"&gt;-f5-&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;NEWEST_REPORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NEWEST_REPORT&lt;/span&gt;&lt;span class="p"&gt;%/&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
mc &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt; minio/allure/&lt;span class="nv"&gt;$PROJECTNAME&lt;/span&gt;/allure-report-history/&lt;span class="nv"&gt;$NEWEST_REPORT&lt;/span&gt; report
&lt;span class="nb"&gt;rm&lt;/span&gt; /opt/nginx/html/index.html
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; report/&lt;span class="nv"&gt;$NEWEST_REPORT&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt; /opt/nginx/html

nginx


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With these two containers and four files, you should be pretty much set to go. &lt;br&gt;
We split our all-in-one-container into two separate containers, that are buildable without a connection to minio.&lt;br&gt;
Our reporter and it's nginx-server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's go k8s
&lt;/h3&gt;

&lt;p&gt;I am assuming, you have a running kubernetes setup, if not, you can take a look at my "&lt;a href="https://github.com/RHabereder/zero-to-k8s" rel="noopener noreferrer"&gt;lazy k8s solution&lt;/a&gt;" to running kubernetes, which can take care of that for you.&lt;/p&gt;

&lt;p&gt;As final part of the tekton integration, you can use this task to run the report-generator (The first Container up there) and deploy the nginx-server via the file that is referenced in the kubectl step. &lt;br&gt;
allure-reporter-task.yaml:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

apiVersion: tekton.dev/v1beta1
kind: Task
metadata: 
  name: allure-reporter-task
  namespace: tekton
spec:
  workspaces: 
    - name: git
  params: 
   - name: minioEndpoint
     default: http://minio.default.svc.cluster.local:9000
   - name: minioAccessKey
     default: minioadmin
   - name: minioSecretKey
     default: minioadmin
   - name: pathToDeploymentYaml
     default: k8s/allure-server.yaml
  steps:
    - name: generate-and-publish-allure-report
      image: allure-report-generator:1.0.0
      command: 
        - /bin/bash
      args:
        - -c
        - |
          cd /workspace/git/
          entrypoint
      env:
        - name: MINIO_ENDPOINT
          value: $(params.minioEndpoint)
        - name: MINIO_ACCESS_KEY
          value: $(params.minioAccessKey)
        - name: MINIO_SECRET_KEY
          value: $(params.minioSecretKey)
    - name: serve-allure-report
      image: lachlanevenson/k8s-kubectl
      command: ["kubectl"]
      args:
        - "apply"
        - "-f"
        - "$(workspaces.git.path)/$(params.pathToDeploymentYaml)"


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To get your allure-server deployed, the following file should be in your apps git-repository, so tekton can find it during a taskrun&lt;/p&gt;

&lt;p&gt;Example of k8s/allure-server.yaml:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allure-server&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allure-server&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allure-server&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allure-server&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-registry/allure-server:1.0.0&lt;/span&gt;
          &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Always&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allure-server&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allure-server&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt; 
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik.containo.us/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IngressRoute&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allure-server-ingress-route&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;entryPoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Host(`localhost`) &amp;amp;&amp;amp; PathPrefix(`/allure`)&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Rule&lt;/span&gt;
    &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allure-server&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
    &lt;span class="na"&gt;middlewares&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allure-stripprefix&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik.containo.us/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Middleware&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allure-stripprefix&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stripPrefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;prefixes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/allure&lt;/span&gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The difference in this approach, in comparison to the Dockerfile I demonstrated up there, I put all the logic of the Dockerfile into entrypoint scripts. &lt;br&gt;
You know, all the copying, report generation, sorting, everything.&lt;/p&gt;

&lt;p&gt;That way I don't have to push the same image over and over into my registry, just because the report was updated. I can just have one reporter image that dynamically pulls the data it needs from storage. &lt;/p&gt;

&lt;p&gt;Also, the images you need can be built by a CI, which normally shouldn't have any access to storage solutions like minio. So a build of the Dockerfiles with a minio-connection would break your build. That's exactly why we put all the networking stuff into the entrypoints, as they will not be executed by your CI.&lt;/p&gt;

&lt;h3&gt;
  
  
  The future
&lt;/h3&gt;

&lt;p&gt;I am already working on a practical demo of allure and tekton with &lt;a href="https://github.com/RHabereder/zero-to-k8s" rel="noopener noreferrer"&gt;zero-to-k8s&lt;/a&gt;. So you don't have to do all the copying and pasting from here. &lt;/p&gt;

&lt;h3&gt;
  
  
  End of story
&lt;/h3&gt;

&lt;p&gt;But for now, I leave you with this. &lt;br&gt;
A random assortment of stuff, just to get some nice shiny UI for your testresults :D &lt;/p&gt;

&lt;p&gt;If you have questions, suggestions, or other feedback, feel free to hammer away at your keyboard :)&lt;/p&gt;

</description>
      <category>java</category>
      <category>devops</category>
      <category>testing</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>How to set up Gitlab to trigger Jenkins on push</title>
      <dc:creator>Raphael Habereder</dc:creator>
      <pubDate>Tue, 16 Jun 2020 09:41:27 +0000</pubDate>
      <link>https://dev.to/habereder/how-to-setup-gitlab-to-trigger-jenkins-on-push-3b9d</link>
      <guid>https://dev.to/habereder/how-to-setup-gitlab-to-trigger-jenkins-on-push-3b9d</guid>
      <description>&lt;p&gt;Last time we built a simple Jenkins CI Pipeline with polling, so our images are always up to date when we push changes to our codebase. &lt;/p&gt;

&lt;p&gt;I don't like using polling for this, and you shouldn't either. The Polling approach is either unreliable if the timing is too slow and incluces too many commits in a single image, or produces unnecessary load if you let it poll every second. &lt;/p&gt;

&lt;p&gt;Imagine your Boss going "hey, is the feature done? How about now? It's been 5 seconds, anything new?".&lt;br&gt;
A) it's annoying and B) you won't get anything done that way, right?&lt;/p&gt;

&lt;p&gt;Rather you would prefer to inform your Boss "hey, the feature is done, take a look!" and go on to do something new, right?&lt;/p&gt;

&lt;p&gt;So let's implement this and notify our CI-Server whenever a commit is pushed. &lt;br&gt;
Don't feel left out if you don't use Gitlab or Jenkins in your projects, most git/ci toolsets offer a feature of this kind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;This is our to-do-list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make DNS-Names for container-communication work, because looking up IPs of containers is annoying&lt;/li&gt;
&lt;li&gt;Boot a Jenkins Instance and create a Job we can trigger via Webhooks&lt;/li&gt;
&lt;li&gt;Boot a Gitlab Instance and create a repo with a demo Dockerfile and Webhook&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So let's get crackin!&lt;/p&gt;

&lt;h3&gt;
  
  
  DNS-Resolution
&lt;/h3&gt;

&lt;p&gt;I could go on a tangent and tell you the grand story, but in short, the default bridge network does no dns resolution because of reasons. If you want dns-resolution for containers by their name, you need to create a custom bridge network. Which thankfully, is very easy to do!&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;docker network create dns-bridge
&lt;span class="nv"&gt;$ &lt;/span&gt;docker network &lt;span class="nb"&gt;ls
&lt;/span&gt;NETWORK ID          NAME                DRIVER              SCOPE
9508cfea746d        bridge              bridge              &lt;span class="nb"&gt;local
&lt;/span&gt;67aa52615190        dns-bridge          bridge              &lt;span class="nb"&gt;local
&lt;/span&gt;ae9791d28429        host                host                &lt;span class="nb"&gt;local
&lt;/span&gt;d2c62d84bc06        none                null                &lt;span class="nb"&gt;local&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That's it, now you have a user-defined bridge that automatically does dns resolution. Let's test it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

docker run -d --name busybox1 --network dns-bridge busybox:1.28 sleep 3600
docker run -d --name busybox2 --network dns-bridge busybox:1.28 sleep 3600

$ docker exec -ti busybox1 ping busybox2
PING busybox2 (172.20.1.2): 56 data bytes
64 bytes from 172.20.1.2: seq=0 ttl=64 time=0.071 ms
64 bytes from 172.20.1.2: seq=1 ttl=64 time=0.057 ms

And the reverse:
$ docker exec -ti busybox1 ping busybox2
PING busybox2 (172.20.1.2): 56 data bytes
64 bytes from 172.20.1.2: seq=0 ttl=64 time=0.071 ms
64 bytes from 172.20.1.2: seq=1 ttl=64 time=0.057 ms


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That's looking pretty good! &lt;/p&gt;

&lt;p&gt;If you want to connect/disconnect an already running container to a network:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ docker run -d --name busybox-ext busybox:1.28 sleep 3600
2efe297352a6ff1e9876a46c853b786a415480235d05df9451672689d580ad6e

$ docker network connect dns-bridge busybox-ext
$ docker network inspect dns-bridge -f "{{range .Containers}}{{println .Name}}{{end}}"
busybox-ext
busybox2
busybox1
$ docker network disconnect dns-bridge busybox-ext
$ docker network inspect dns-bridge -f "{{range .Containers}}{{println .Name}}{{end}}"
busybox2
busybox1


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Alright, let's go on to setup our Jenkins instance&lt;/p&gt;

&lt;h3&gt;
  
  
  Jenkins
&lt;/h3&gt;

&lt;p&gt;For this post I built a custom Docker-In-Docker Jenkins based on Alpine, which I will tell you, was a horrible experience. &lt;br&gt;
I'm not happy about this dockerfile, so don't crucify me please. &lt;br&gt;
While I could put it up on github or something, I don't want you to clone a random repo and just run it without having at least taken a look at it. So I am going to be the one bad teacher nobody likes and let you copy the two files you need from here :)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  bash &lt;span class="se"&gt;\
&lt;/span&gt;  coreutils &lt;span class="se"&gt;\
&lt;/span&gt;  curl &lt;span class="se"&gt;\
&lt;/span&gt;  git &lt;span class="se"&gt;\
&lt;/span&gt;  git-lfs &lt;span class="se"&gt;\
&lt;/span&gt;  openssh-client &lt;span class="se"&gt;\
&lt;/span&gt;  tini &lt;span class="se"&gt;\
&lt;/span&gt;  ttf-dejavu &lt;span class="se"&gt;\
&lt;/span&gt;  tzdata &lt;span class="se"&gt;\
&lt;/span&gt;  unzip &lt;span class="se"&gt;\
&lt;/span&gt;  openjdk11-jdk &lt;span class="se"&gt;\
&lt;/span&gt;  shadow &lt;span class="se"&gt;\ &lt;/span&gt;
  docker 

&lt;span class="c"&gt;# Install Gosu&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; GOSU_VERSION 1.12&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eux&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="se"&gt;\
&lt;/span&gt;    apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nt"&gt;--virtual&lt;/span&gt; .gosu-deps &lt;span class="se"&gt;\
&lt;/span&gt;        ca-certificates &lt;span class="se"&gt;\
&lt;/span&gt;        dpkg &lt;span class="se"&gt;\
&lt;/span&gt;        gnupg &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nv"&gt;dpkgArch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F-&lt;/span&gt; &lt;span class="s1"&gt;'{ print $NF }'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    wget &lt;span class="nt"&gt;-O&lt;/span&gt; /usr/local/bin/gosu &lt;span class="s2"&gt;"https://github.com/tianon/gosu/releases/download/&lt;/span&gt;&lt;span class="nv"&gt;$GOSU_VERSION&lt;/span&gt;&lt;span class="s2"&gt;/gosu-&lt;/span&gt;&lt;span class="nv"&gt;$dpkgArch&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    wget &lt;span class="nt"&gt;-O&lt;/span&gt; /usr/local/bin/gosu.asc &lt;span class="s2"&gt;"https://github.com/tianon/gosu/releases/download/&lt;/span&gt;&lt;span class="nv"&gt;$GOSU_VERSION&lt;/span&gt;&lt;span class="s2"&gt;/gosu-&lt;/span&gt;&lt;span class="nv"&gt;$dpkgArch&lt;/span&gt;&lt;span class="s2"&gt;.asc"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="c"&gt;# verify the signature&lt;/span&gt;
    export GNUPGHOME="$(mktemp -d)"; \
    gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
    gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
    command -v gpgconf &amp;amp;&amp;amp; gpgconf --kill all || :; \
    rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
    \
# clean up fetch dependencies
    apk del --no-network .gosu-deps; \
    \
    chmod +x /usr/local/bin/gosu; \
# verify that the binary works
    gosu --version; \
    gosu nobody true

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; user=jenkins&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; group=jenkins&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; uid=1001&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; gid=1001&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; http_port=8080&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; agent_port=50000&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JENKINS_HOME=/var/jenkins_home&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; REF=/usr/share/jenkins/ref&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JENKINS_HOME $JENKINS_HOME&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JENKINS_SLAVE_AGENT_PORT ${agent_port}&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; REF $REF&lt;/span&gt;

&lt;span class="c"&gt;# Jenkins is run with user `jenkins`, uid = 1000&lt;/span&gt;
&lt;span class="c"&gt;# If you bind mount a volume from the host or a data container,&lt;/span&gt;
&lt;span class="c"&gt;# ensure you use the same uid&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$JENKINS_HOME&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;:&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;gid&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;$JENKINS_HOME&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; addgroup &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;gid&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;group&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$JENKINS_HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-G&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;group&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /bin/bash &lt;span class="nt"&gt;-D&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; 

&lt;span class="c"&gt;# Jenkins home directory is a volume, so configuration and build history&lt;/span&gt;
&lt;span class="c"&gt;# can be persisted and survive image upgrades&lt;/span&gt;
&lt;span class="k"&gt;VOLUME&lt;/span&gt;&lt;span class="s"&gt; $JENKINS_HOME&lt;/span&gt;

&lt;span class="c"&gt;# $REF (defaults to `/usr/share/jenkins/ref/`) contains all reference configuration we want&lt;/span&gt;
&lt;span class="c"&gt;# to set on a fresh new installation. Use it to bundle additional plugins&lt;/span&gt;
&lt;span class="c"&gt;# or config file with your custom jenkins Docker image.&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REF&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/init.groovy.d

&lt;span class="c"&gt;# jenkins version being bundled in this docker image&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JENKINS_VERSION&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JENKINS_VERSION ${JENKINS_VERSION:-2.222.4}&lt;/span&gt;

&lt;span class="c"&gt;# jenkins.war checksum, download will be validated using it&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JENKINS_SHA=6c95721b90272949ed8802cab8a84d7429306f72b180c5babc33f5b073e1c47c&lt;/span&gt;

&lt;span class="c"&gt;# Can be used to customize where jenkins.war gets downloaded from&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JENKINS_URL=https://repo.jenkins-ci.org/public/org/jenkins-ci/main/jenkins-war/${JENKINS_VERSION}/jenkins-war-${JENKINS_VERSION}.war&lt;/span&gt;

&lt;span class="c"&gt;# Could use ADD but this one does not check Last-Modified header neither does it allow to control checksum&lt;/span&gt;
&lt;span class="c"&gt;# See https://github.com/docker/docker/issues/8331&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JENKINS_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/share/jenkins/jenkins.war &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JENKINS_SHA&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;  /usr/share/jenkins/jenkins.war"&lt;/span&gt; | &lt;span class="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; -

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JENKINS_UC https://updates.jenkins.io&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimental&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementals&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$JENKINS_HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REF&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# For main web interface:&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; ${http_port}&lt;/span&gt;

&lt;span class="c"&gt;# Will be used by attached agents:&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; ${agent_port}&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; COPY_REFERENCE_FILE_LOG $JENKINS_HOME/copy_reference_file.log&lt;/span&gt;

&lt;span class="c"&gt;# Download and place scripts needed to run&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl https://raw.githubusercontent.com/jenkinsci/docker/master/jenkins-support &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/local/bin/jenkins-support &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    curl https://raw.githubusercontent.com/jenkinsci/docker/master/jenkins.sh &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/local/bin/jenkins.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    curl https://raw.githubusercontent.com/jenkinsci/docker/master/tini-shim.sh &lt;span class="nt"&gt;-o&lt;/span&gt; /bin/tini &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    curl https://raw.githubusercontent.com/jenkinsci/docker/master/plugins.sh &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/local/bin/plugins.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    curl https://raw.githubusercontent.com/jenkinsci/docker/master/install-plugins.sh &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/local/bin/install-plugins.sh

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=${user} entrypoint.sh /entrypoint.sh&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/local/bin/install-plugins.sh /usr/local/bin/plugins.sh /usr/local/bin/jenkins.sh /bin/tini /usr/local/bin/jenkins-support
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /entrypoint.sh

&lt;span class="c"&gt;# Stay root, the entrypoint drops down to User jenkins via gosu&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/entrypoint.sh"]&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;entrypoint.sh&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="c"&gt;# By: Brandon Mitchell &amp;lt;public@bmitch.net&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;# License: MIT&lt;/span&gt;
&lt;span class="c"&gt;# Source Repo: https://github.com/sudo-bmitch/jenkins-docker&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt;

&lt;span class="c"&gt;# configure script to call original entrypoint&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; tini &lt;span class="nt"&gt;--&lt;/span&gt; /usr/local/bin/jenkins.sh &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# In Prod, this may be configured with a GID already matching the container&lt;/span&gt;
&lt;span class="c"&gt;# allowing the container to be run directly as Jenkins. In Dev, or on unknown&lt;/span&gt;
&lt;span class="c"&gt;# environments, run the container as root to automatically correct docker&lt;/span&gt;
&lt;span class="c"&gt;# group in container to match the docker.sock GID mounted from the host.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
  &lt;span class="c"&gt;# get gid of docker socket file&lt;/span&gt;
  &lt;span class="nv"&gt;SOCK_DOCKER_GID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-ng&lt;/span&gt; /var/run/docker.sock | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-f3&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;

  &lt;span class="c"&gt;# get group of docker inside container&lt;/span&gt;
  &lt;span class="nv"&gt;CUR_DOCKER_GID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;getent group docker | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-f3&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;: &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;

  &lt;span class="c"&gt;# if they don't match, adjust&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SOCK_DOCKER_GID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SOCK_DOCKER_GID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CUR_DOCKER_GID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;groupmod &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SOCK_DOCKER_GID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; docker
  &lt;span class="k"&gt;fi
  if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;groups &lt;/span&gt;jenkins | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; docker&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker jenkins
  &lt;span class="k"&gt;fi&lt;/span&gt;

  &lt;span class="c"&gt;#If you run on MacOS&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;groups &lt;/span&gt;jenkins | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; staff&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; staff jenkins
  &lt;span class="k"&gt;fi&lt;/span&gt;
  &lt;span class="c"&gt;# Add call to gosu to drop from root user to jenkins user&lt;/span&gt;
  &lt;span class="c"&gt;# when running original entrypoint&lt;/span&gt;
  &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; gosu jenkins &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# replace the current pid 1 with original entrypoint&lt;/span&gt;
&lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Build and run our Jenkins image:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;# We need to create a directory for Jenkins to save his data to&lt;/span&gt;
&lt;span class="c"&gt;# Since to container runs with UID:GID 1001:1001, the folder also needs to get correct permissions &lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/jenkins &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown &lt;/span&gt;1001:1001 &lt;span class="nv"&gt;$HOME&lt;/span&gt;/jenkins

docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myjenkins &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
           &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/jenkins:/var/jenkins_home &lt;span class="se"&gt;\ &lt;/span&gt;
           &lt;span class="nt"&gt;-v&lt;/span&gt; /var/run/docker.sock:/var/run/docker.sock &lt;span class="se"&gt;\&lt;/span&gt;
           &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="se"&gt;\&lt;/span&gt;
           &lt;span class="nt"&gt;--name&lt;/span&gt; jenkins &lt;span class="se"&gt;\&lt;/span&gt;
           &lt;span class="nt"&gt;--network&lt;/span&gt; dns-bridge &lt;span class="se"&gt;\&lt;/span&gt;
           &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="se"&gt;\&lt;/span&gt;
           myjenkins 


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;What does this do?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The mount to $HOME/jenkins makes sure you don't have to reconfigure jenkins every time you kill/stop the jenkins container. It's just convenience. &lt;/li&gt;
&lt;li&gt;We mount the docker.sock into Jenkins, so Jenkins can build our docker images without having it's own functioning docker-runtime. This concept is called docker in docker.&lt;/li&gt;
&lt;li&gt;The rest should be self-explanatory, we publish jenkins port so we can access it from the host, attach it to the network bridge we created and make sure the container restart on failure, unless we stop it ourselves&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Gitlab
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;#Where you want to store data&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITLAB_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/gitlab

docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 443:443 &lt;span class="nt"&gt;-p&lt;/span&gt; 80:80 &lt;span class="nt"&gt;-p&lt;/span&gt; 22:22 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; gitlab &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; dns-bridge &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$GITLAB_HOME&lt;/span&gt;/config:/etc/gitlab &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$GITLAB_HOME&lt;/span&gt;/logs:/var/log/gitlab &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$GITLAB_HOME&lt;/span&gt;/data:/var/opt/gitlab &lt;span class="se"&gt;\&lt;/span&gt;
  gitlab/gitlab-ce:latest


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And yes, I know, this breaks the rule of "build it yourself", but I can't for the life of me get gitlab running on alpine yet, so for this demonstration I beg you to bear with me this one time. &lt;br&gt;
Of course, I will update this post accordingly, once I got gitlab to work smoothly on alpine :)&lt;/p&gt;

&lt;p&gt;If you don't want the containers to play around in your filesystem, just remove the mounts. &lt;br&gt;
&lt;em&gt;Note: Again, you'll have to set everything up again, if you restart the containers without their data persisted somewhere&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;The Gitlab omnibus image takes a while to boot, you can monitor it via &lt;code&gt;docker logs -f gitlab&lt;/code&gt; to see when it's ready to use.&lt;br&gt;
After it's booted up, you can open the &lt;a href="http://localhost/" rel="noopener noreferrer"&gt;Gitlab UI&lt;/a&gt; in your favorite browser and set a password for the user &lt;strong&gt;root&lt;/strong&gt;.&lt;br&gt;
Then you are ready to create some repos :)&lt;/p&gt;

&lt;p&gt;More info on the Gitlab Docker Images can be found &lt;a href="https://docs.gitlab.com/omnibus/docker/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo Repository
&lt;/h3&gt;

&lt;p&gt;Now that our shiny new Gitlab is running, let's create a demo repository &lt;a href="http://localhost/projects/new" rel="noopener noreferrer"&gt;here&lt;/a&gt; and commit the following demo Dockerfile to it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:latest&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'#!/bin/sh'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/hello.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'echo "Hello $1"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /tmp/hello.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chmod&lt;/span&gt; +x /tmp/hello.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;
    chown nobody. /tmp/hello.sh 

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nobody&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/bin/sh"]&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/tmp/hello.sh", "Me!"]&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is a very simple container that just echos a greeting, which will suffice to demonstrate our Git-Hook.&lt;/p&gt;

&lt;p&gt;Add, commit and push the file for now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo-Job
&lt;/h3&gt;

&lt;p&gt;Now is the time to go back to our Jenkins demo-job, we wanted to set up earlier. &lt;/p&gt;

&lt;p&gt;Now that we have source to build, we can use it in our job. &lt;br&gt;
Create a pipeline with your favorite name &lt;a href="http://localhost:8080/view/all/newJob" rel="noopener noreferrer"&gt;here&lt;/a&gt; with the following content:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;

&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Git Checkout"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="nl"&gt;credentialsId:&lt;/span&gt; &lt;span class="s1"&gt;'gitlab'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;url:&lt;/span&gt; &lt;span class="s1"&gt;'http://gitlab/root/demo'&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Docker Build"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'awesome-image'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Docker Push"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withRegistry&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"http://registry.local:5000"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"awesome-tag-${env.BUILD_NUMBER}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"latest"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you probably saw, we are missing two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;credentials for gitlab&lt;/li&gt;
&lt;li&gt;the registry "registry.local:5000"&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Jenkins Gitlab Credentials
&lt;/h4&gt;

&lt;p&gt;Go &lt;a href="http://localhost:8080/credentials/store/system/domain/_/newCredentials" rel="noopener noreferrer"&gt;here&lt;/a&gt; and add the credentials you set for gitlab earlier:&lt;/p&gt;

&lt;p&gt;username and password are obvious. ID is the "shortname" you will use in your pipelines. In the example it is "gitlab". &lt;br&gt;
Description is just that, a human readable description for easy distinction of the various credentials you can have. &lt;/p&gt;

&lt;p&gt;As for the rest of the configuration, like the plugins and docker runtime, you can take a look at the previous post as a guideline. &lt;/p&gt;

&lt;h4&gt;
  
  
  Registry
&lt;/h4&gt;

&lt;p&gt;To fire up and connect our registry to our dns-bridge:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

docker run -d -p 5000:5000 --name registry.local --network dns-bridge registry:2


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Since we are running docker-in-docker, the push works from our host. So our host has to know the dns-name registry.local too. &lt;br&gt;
Make sure it is in our hosts with &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

cat /etc/hosts


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If it isn't there, just add it via &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

echo '127.0.0.1 registry.local' | sudo tee -a /etc/hosts


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That should fix it. Now you can trigger the build and enjoy the show!&lt;/p&gt;

&lt;p&gt;It should be a success, and you can docker pull it anytime from our registry :)&lt;/p&gt;

&lt;p&gt;Alright, on to the home-stretch! We are done with all the prep-work, so let's get to what we actually wanted to achieve.&lt;/p&gt;

&lt;h4&gt;
  
  
  Set up the Endpoint for Jenkins
&lt;/h4&gt;

&lt;p&gt;To enable Jenkins-Jobs to be triggered via HTTP-Request, you need to do a few things. &lt;/p&gt;

&lt;p&gt;First, check the following box in your job and define a secret token. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fubdeykcckbf99lt3fxfn.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fubdeykcckbf99lt3fxfn.PNG" alt="Jenkins Job-Token"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we need an API-Token to authenticate with Jenkins in HTTP-Requests, without having to use the actual password of our admin-user. &lt;br&gt;
You can create a token &lt;a href="http://localhost:8080/user/admin/configure" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;br&gt;
Save it somewhere you can copy it from later, because that little bugger is gone, once you navigate away from the current page. &lt;/p&gt;

&lt;p&gt;As a nice benefit, you can monitor later on how many times the token has been used. Neat!&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up the Webhook in Gitlab
&lt;/h3&gt;

&lt;p&gt;This is it, the end of the road, we are near the finish line!&lt;/p&gt;

&lt;p&gt;Gitlab is smart, they don't want to permit Webhooks to spam the local network of a gitlab instance to prevent damage. But we want to allow it to do so with Jenkins, because otherwise that would render this whole mechanic moot for us. &lt;br&gt;
So go to &lt;a href="http://localhost/admin/application_settings/network" rel="noopener noreferrer"&gt;the admin panel&lt;/a&gt; and in the "Outbound Requests" section, add "jenkins" to the "Whitelist to allow requests to the local network from hooks and services". &lt;br&gt;
&lt;strong&gt;Don't check the box that allows all adresses in the local network! Just add jenkins to the whitelist&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, to create a webhook, open your project and on the left hand side go to Settings -&amp;gt; Webhook and configure your webhook like this: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff2q2hy9m48ylptr0xyhi.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff2q2hy9m48ylptr0xyhi.PNG" alt="Gitlab Webhook Conf"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For easy copy and paste, take this template and fill in your values:&lt;br&gt;
&lt;strong&gt;http://:@:&amp;lt;8080&amp;gt;/job//build?token=&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Test
&lt;/h3&gt;

&lt;p&gt;Now our interconnectivity between gitlab and jenkins is automated for this job and repository. So let's test it!&lt;/p&gt;

&lt;p&gt;Modify your Dockerfile, commit and push it and a few seconds later you should have a docker image in your local registry that reflects your change!&lt;/p&gt;

&lt;h2&gt;
  
  
  Outro
&lt;/h2&gt;

&lt;p&gt;I have to admit, this was a little bit of work to get running locally, especially the Jenkins-DIND Image for MacOS.. &lt;br&gt;
But I do think this made you and me a little bit smarter, so I hope this helps you on your journey to transition to containers.&lt;/p&gt;

&lt;p&gt;If not, shoot me a message, leave a comment or ask questions.&lt;br&gt;
If there is one thing I have, it's time, patience and a talent for "making things work" or breaking them :D &lt;/p&gt;

&lt;p&gt;Next time I'll probably write about how we can scan our images for CVEs, that draft is nearly done :)&lt;/p&gt;

</description>
      <category>containers</category>
      <category>docker</category>
      <category>automation</category>
      <category>devops</category>
    </item>
    <item>
      <title>I work with Containers, Automation and Cloud Stacks for up to 10 years now, ask me anything</title>
      <dc:creator>Raphael Habereder</dc:creator>
      <pubDate>Mon, 15 Jun 2020 13:30:30 +0000</pubDate>
      <link>https://dev.to/habereder/i-work-with-containers-automation-and-cloud-stacks-for-up-to-10-years-now-ask-me-anything-265o</link>
      <guid>https://dev.to/habereder/i-work-with-containers-automation-and-cloud-stacks-for-up-to-10-years-now-ask-me-anything-265o</guid>
      <description>&lt;p&gt;While I continue to work on about 5 posts in parallel, I thought this would be fun to do. &lt;/p&gt;

&lt;p&gt;While it normally only works with celebrities, in the worst case noone answers and I can shamefully delete this post. Then I'd have to wait for the day I become famous for crashing some thought-to-be-indestructible software by pure accident. &lt;/p&gt;

&lt;p&gt;I am by no means an Expert in these fields (they are huuuuge), but I have been around the block a while. I'm the Kid noone talks to, because I take the whole automation thing too far and put anything that sounds fun into containers to deploy it on azure or my k8s clusters somewhere. &lt;/p&gt;

&lt;p&gt;So if you got fun questions, or need something containerized/automated feel free to hammer away at your keyboard, I take requests/challenges :)&lt;/p&gt;

</description>
      <category>container</category>
      <category>cloud</category>
      <category>automation</category>
      <category>ama</category>
    </item>
    <item>
      <title>How to build a basic Docker CI/CD Pipeline with Jenkins</title>
      <dc:creator>Raphael Habereder</dc:creator>
      <pubDate>Fri, 12 Jun 2020 09:23:20 +0000</pubDate>
      <link>https://dev.to/habereder/how-to-build-a-basic-docker-ci-cd-pipeline-with-jenkins-3m5n</link>
      <guid>https://dev.to/habereder/how-to-build-a-basic-docker-ci-cd-pipeline-with-jenkins-3m5n</guid>
      <description>&lt;p&gt;In the last post we talked about the Dos and Don'ts of containers. &lt;/p&gt;

&lt;p&gt;We established the following example hierarchy of images:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

minimal-baseimage (for example ubuntu, alpine, centos)
|__ nginx-baseimage
|  |__ awesome-website-container
|__ quarkus-baseimage
|  |__ awesome-java-microservice
|__ python-baseimage
   |__ awesome-flask-webapp


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Maybe you dabbled a bit and built a few containers. &lt;br&gt;
A few weeks pass. &lt;br&gt;
You are sick of updating everything yourself. &lt;br&gt;
&lt;strong&gt;This needs to be automated in some way, you want to focus on the real thing, your awesome apps!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So let's build a simple pipeline with Jenkins that takes care of patching your Images, and later on, maybe even deploy them for you! Automagically. &lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Install jenkins with your favorite package manager:&lt;/p&gt;

&lt;p&gt;Debian:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

wget &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-O&lt;/span&gt; - https://pkg.jenkins.io/debian-stable/jenkins.io.key | &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-key add -
&lt;span class="nb"&gt;sudo &lt;/span&gt;sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'echo deb https://pkg.jenkins.io/debian-stable binary/ &amp;gt; \
    /etc/apt/sources.list.d/jenkins.list'&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;jenkins openjdk-11-jdk-headless


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Or if you are on MacOS:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

brew cask install homebrew/cask-versions/adoptopenjdk8
brew install jenkins-lts
brew services start jenkins-lts


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you want to run Jenkins on docker, it's going to be a bit more complicated, but it's doable. It just hastened my aging process by about 200 years to make this work on MacOS too. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  bash &lt;span class="se"&gt;\
&lt;/span&gt;  coreutils &lt;span class="se"&gt;\
&lt;/span&gt;  curl &lt;span class="se"&gt;\
&lt;/span&gt;  git &lt;span class="se"&gt;\
&lt;/span&gt;  git-lfs &lt;span class="se"&gt;\
&lt;/span&gt;  openssh-client &lt;span class="se"&gt;\
&lt;/span&gt;  tini &lt;span class="se"&gt;\
&lt;/span&gt;  ttf-dejavu &lt;span class="se"&gt;\
&lt;/span&gt;  tzdata &lt;span class="se"&gt;\
&lt;/span&gt;  unzip &lt;span class="se"&gt;\
&lt;/span&gt;  openjdk11-jdk &lt;span class="se"&gt;\
&lt;/span&gt;  shadow &lt;span class="se"&gt;\ &lt;/span&gt;
  docker 

&lt;span class="c"&gt;# Install Gosu&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; GOSU_VERSION 1.12&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eux&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="se"&gt;\
&lt;/span&gt;    apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nt"&gt;--virtual&lt;/span&gt; .gosu-deps &lt;span class="se"&gt;\
&lt;/span&gt;        ca-certificates &lt;span class="se"&gt;\
&lt;/span&gt;        dpkg &lt;span class="se"&gt;\
&lt;/span&gt;        gnupg &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nv"&gt;dpkgArch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F-&lt;/span&gt; &lt;span class="s1"&gt;'{ print $NF }'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    wget &lt;span class="nt"&gt;-O&lt;/span&gt; /usr/local/bin/gosu &lt;span class="s2"&gt;"https://github.com/tianon/gosu/releases/download/&lt;/span&gt;&lt;span class="nv"&gt;$GOSU_VERSION&lt;/span&gt;&lt;span class="s2"&gt;/gosu-&lt;/span&gt;&lt;span class="nv"&gt;$dpkgArch&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    wget &lt;span class="nt"&gt;-O&lt;/span&gt; /usr/local/bin/gosu.asc &lt;span class="s2"&gt;"https://github.com/tianon/gosu/releases/download/&lt;/span&gt;&lt;span class="nv"&gt;$GOSU_VERSION&lt;/span&gt;&lt;span class="s2"&gt;/gosu-&lt;/span&gt;&lt;span class="nv"&gt;$dpkgArch&lt;/span&gt;&lt;span class="s2"&gt;.asc"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="c"&gt;# verify the signature&lt;/span&gt;
    export GNUPGHOME="$(mktemp -d)"; \
    gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
    gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
    command -v gpgconf &amp;amp;&amp;amp; gpgconf --kill all || :; \
    rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
    \
# clean up fetch dependencies
    apk del --no-network .gosu-deps; \
    \
    chmod +x /usr/local/bin/gosu; \
# verify that the binary works
    gosu --version; \
    gosu nobody true

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; user=jenkins&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; group=jenkins&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; uid=1001&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; gid=1001&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; http_port=8080&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; agent_port=50000&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JENKINS_HOME=/var/jenkins_home&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; REF=/usr/share/jenkins/ref&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JENKINS_HOME $JENKINS_HOME&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JENKINS_SLAVE_AGENT_PORT ${agent_port}&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; REF $REF&lt;/span&gt;

&lt;span class="c"&gt;# Jenkins is run with user `jenkins`, uid = 1000&lt;/span&gt;
&lt;span class="c"&gt;# If you bind mount a volume from the host or a data container,&lt;/span&gt;
&lt;span class="c"&gt;# ensure you use the same uid&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$JENKINS_HOME&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;:&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;gid&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;$JENKINS_HOME&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; addgroup &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;gid&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;group&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$JENKINS_HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-G&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;group&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /bin/bash &lt;span class="nt"&gt;-D&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; 

&lt;span class="c"&gt;# Jenkins home directory is a volume, so configuration and build history&lt;/span&gt;
&lt;span class="c"&gt;# can be persisted and survive image upgrades&lt;/span&gt;
&lt;span class="k"&gt;VOLUME&lt;/span&gt;&lt;span class="s"&gt; $JENKINS_HOME&lt;/span&gt;

&lt;span class="c"&gt;# $REF (defaults to `/usr/share/jenkins/ref/`) contains all reference configuration we want&lt;/span&gt;
&lt;span class="c"&gt;# to set on a fresh new installation. Use it to bundle additional plugins&lt;/span&gt;
&lt;span class="c"&gt;# or config file with your custom jenkins Docker image.&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REF&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/init.groovy.d

&lt;span class="c"&gt;# jenkins version being bundled in this docker image&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JENKINS_VERSION&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JENKINS_VERSION ${JENKINS_VERSION:-2.222.4}&lt;/span&gt;

&lt;span class="c"&gt;# jenkins.war checksum, download will be validated using it&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JENKINS_SHA=6c95721b90272949ed8802cab8a84d7429306f72b180c5babc33f5b073e1c47c&lt;/span&gt;

&lt;span class="c"&gt;# Can be used to customize where jenkins.war gets downloaded from&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JENKINS_URL=https://repo.jenkins-ci.org/public/org/jenkins-ci/main/jenkins-war/${JENKINS_VERSION}/jenkins-war-${JENKINS_VERSION}.war&lt;/span&gt;

&lt;span class="c"&gt;# Could use ADD but this one does not check Last-Modified header neither does it allow to control checksum&lt;/span&gt;
&lt;span class="c"&gt;# See https://github.com/docker/docker/issues/8331&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JENKINS_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/share/jenkins/jenkins.war &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JENKINS_SHA&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;  /usr/share/jenkins/jenkins.war"&lt;/span&gt; | &lt;span class="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; -

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JENKINS_UC https://updates.jenkins.io&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimental&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementals&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$JENKINS_HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REF&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# For main web interface:&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; ${http_port}&lt;/span&gt;

&lt;span class="c"&gt;# Will be used by attached agents:&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; ${agent_port}&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; COPY_REFERENCE_FILE_LOG $JENKINS_HOME/copy_reference_file.log&lt;/span&gt;

&lt;span class="c"&gt;# Download and place scripts needed to run&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl https://raw.githubusercontent.com/jenkinsci/docker/master/jenkins-support &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/local/bin/jenkins-support &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    curl https://raw.githubusercontent.com/jenkinsci/docker/master/jenkins.sh &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/local/bin/jenkins.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    curl https://raw.githubusercontent.com/jenkinsci/docker/master/tini-shim.sh &lt;span class="nt"&gt;-o&lt;/span&gt; /bin/tini &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    curl https://raw.githubusercontent.com/jenkinsci/docker/master/plugins.sh &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/local/bin/plugins.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    curl https://raw.githubusercontent.com/jenkinsci/docker/master/install-plugins.sh &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/local/bin/install-plugins.sh

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=${user} entrypoint.sh /entrypoint.sh&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/local/bin/install-plugins.sh /usr/local/bin/plugins.sh /usr/local/bin/jenkins.sh /bin/tini /usr/local/bin/jenkins-support
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /entrypoint.sh

&lt;span class="c"&gt;# Stay root, the entrypoint drops down to User jenkins via gosu&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/entrypoint.sh"]&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;entrypoint.sh&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="c"&gt;# Stolen from: Brandon Mitchell &amp;lt;public@bmitch.net&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;# License: MIT&lt;/span&gt;
&lt;span class="c"&gt;# Source Repo: https://github.com/sudo-bmitch/jenkins-docker&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt;

&lt;span class="c"&gt;# configure script to call original entrypoint&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; tini &lt;span class="nt"&gt;--&lt;/span&gt; /usr/local/bin/jenkins.sh &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# In Prod, this may be configured with a GID already matching the container&lt;/span&gt;
&lt;span class="c"&gt;# allowing the container to be run directly as Jenkins. In Dev, or on unknown&lt;/span&gt;
&lt;span class="c"&gt;# environments, run the container as root to automatically correct docker&lt;/span&gt;
&lt;span class="c"&gt;# group in container to match the docker.sock GID mounted from the host.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
  &lt;span class="c"&gt;# get gid of docker socket file&lt;/span&gt;
  &lt;span class="nv"&gt;SOCK_DOCKER_GID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-ng&lt;/span&gt; /var/run/docker.sock | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-f3&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;

  &lt;span class="c"&gt;# get group of docker inside container&lt;/span&gt;
  &lt;span class="nv"&gt;CUR_DOCKER_GID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;getent group docker | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-f3&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;: &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;

  &lt;span class="c"&gt;# if they don't match, adjust&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SOCK_DOCKER_GID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SOCK_DOCKER_GID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CUR_DOCKER_GID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;groupmod &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SOCK_DOCKER_GID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; docker
  &lt;span class="k"&gt;fi
  if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;groups &lt;/span&gt;jenkins | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; docker&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker jenkins
  &lt;span class="k"&gt;fi&lt;/span&gt;

  &lt;span class="c"&gt;#If you run on MacOS&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;groups &lt;/span&gt;jenkins | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; staff&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; staff jenkins
  &lt;span class="k"&gt;fi&lt;/span&gt;
  &lt;span class="c"&gt;# Add call to gosu to drop from root user to jenkins user&lt;/span&gt;
  &lt;span class="c"&gt;# when running original entrypoint&lt;/span&gt;
  &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; gosu jenkins &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# replace the current pid 1 with original entrypoint&lt;/span&gt;
&lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Build and run our Jenkins image:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;# We need to create a directory for Jenkins to save his data to&lt;/span&gt;
&lt;span class="c"&gt;# Since to container runs with UID:GID 1001:1001&lt;/span&gt;
&lt;span class="c"&gt;# The folder also needs to get correct permissions set&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/jenkins &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown &lt;/span&gt;1001:1001 &lt;span class="nv"&gt;$HOME&lt;/span&gt;/jenkins

docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myjenkins &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
           &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/jenkins:/var/jenkins_home &lt;span class="se"&gt;\ &lt;/span&gt;
           &lt;span class="nt"&gt;-v&lt;/span&gt; /var/run/docker.sock:/var/run/docker.sock &lt;span class="se"&gt;\&lt;/span&gt;
           &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="se"&gt;\&lt;/span&gt;
           &lt;span class="nt"&gt;--name&lt;/span&gt; jenkins &lt;span class="se"&gt;\&lt;/span&gt;
           &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="se"&gt;\&lt;/span&gt;
           myjenkins 


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you run a different system (sorry, I can't provide them all for you, it would take me days :( ), there is probably a guide for you out there, just as simple as these few lines. &lt;/p&gt;

&lt;p&gt;Open the &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;Jenkins UI&lt;/a&gt; in your awesome browser of choice and enter the password you can find in the location that jenkins tells you.  &lt;/p&gt;

&lt;p&gt;If it's not there, these places are usually a safe bet:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Linux:
/var/log/jenkins/jenkins.log, 

MacOS:
~/.jenkins/secrets/initialAdminPassword

Docker:
docker logs jenkins


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Hammer it in and go on to install the suggested plugins. Depending on your machine, it's now your final chance to grab a cup of coffee, before we dive in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plugins, plugins, plugins
&lt;/h2&gt;

&lt;p&gt;Next, we need some awesome plugins.&lt;br&gt;
Just go via the Jenkins GUI -&amp;gt;  Manage Jenkins -&amp;gt; Manage Plugins&lt;br&gt;
Select the tab "Available", put Docker into the filter in the upper right corner and select the following Plugins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker Commons&lt;/li&gt;
&lt;li&gt;Docker Pipeline&lt;/li&gt;
&lt;li&gt;Docker API
&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;docker-build-step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install without restart and wait a bit.&lt;br&gt;
These will set you up fine for your first simple docker Pipelines. &lt;/p&gt;

&lt;p&gt;Now we have to configure jenkins to find the docker-runtime to build our images with. &lt;/p&gt;

&lt;p&gt;This can be done &lt;a href="http://localhost:8080/configureTools/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. After installing the plugins, you get the new section "Docker", where you can Add Docker Installations. So go ahead and push that button. &lt;br&gt;
Give it a name, I chose "Docker CE 19.03" and leave the installation root empty. Jenkins should find docker on the $PATH itself. &lt;/p&gt;

&lt;h2&gt;
  
  
  Pipelines
&lt;/h2&gt;

&lt;p&gt;On to the next step, let's create a pipeline. &lt;/p&gt;

&lt;p&gt;Via Jenkins -&amp;gt; New Item you'll get to a page that will let you specify which kind of item you want to create. Select Pipeline, give the puppy a nice name and hit OK.&lt;/p&gt;

&lt;p&gt;Scroll down until you see this: &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fojgv13xv1iqozyg3utin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fojgv13xv1iqozyg3utin.png" alt="Jenkins Pipeline Input"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's get this show on the road!&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;

&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;   
    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Clone repository'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Missing Credentials can be added via UI &lt;/span&gt;
        &lt;span class="c1"&gt;// Look at the bottom of the box for a link called "Pipeline-Syntax"&lt;/span&gt;
        &lt;span class="c1"&gt;// If you don't have much Jenkins experience, &lt;/span&gt;
        &lt;span class="c1"&gt;// there you can generate pipelines with a few Dropdowns and Textboxes&lt;/span&gt;
        &lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="nl"&gt;credentialsId:&lt;/span&gt; &lt;span class="s1"&gt;'git'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;url:&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;your git url&amp;gt;'&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Build image'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// If you have multiple Dockerfiles in your Project, use this:&lt;/span&gt;
        &lt;span class="c1"&gt;// app = docker.build("my-ubuntu-base", "-f Dockerfile.base .")&lt;/span&gt;

        &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"my-ubuntu-base"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Test image'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;inside&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'echo "Tests passed"'&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Push image'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withRegistry&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://registry.local:5000'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"18.04-${env.BUILD_NUMBER}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"latest"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This file can be copied easily, since you don't have to change a lot. If you want to go the extra mile, make it a parameterized job and put the variables in there, to be filled via REST for example. &lt;/p&gt;

&lt;p&gt;You might have noticed it, but we push to a registry called &lt;strong&gt;"registry.local:5000"&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;If you don't want to push your images into dockerhub right away, or have no other registry of your own, we can fire one up real quick. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nt"&gt;--restart&lt;/span&gt; always registry.local &lt;span class="nt"&gt;-p&lt;/span&gt; 5000:5000 registry:2


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To use this registry with a nice dns-name, just run this: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'127.0.0.1 registry.local'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/hosts


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To make sure the registry works, you have to tell docker to allow it, as an "insecure registry". &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Linux: echo '{ "insecure-registries": ["registry.local:5000"] }' | sudo tee -a /etc/docker/daemon.json


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;On Desktops you can add this via the Docker Preferences UI&lt;/p&gt;

&lt;p&gt;To get back to our pipelines, if you remember our imaginary Image-Hierarchy, we would need 3 jobs:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

minimal-baseimage
|__ nginx-baseimage
   |__ awesome-website-container


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Go ahead and copy/hack away, I'll wait for you. &lt;/p&gt;

&lt;p&gt;That was easy, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Automate it
&lt;/h2&gt;

&lt;p&gt;Now let's be honest, nobody likes pushing build buttons regularly, so let's automate this. &lt;/p&gt;

&lt;h3&gt;
  
  
  Minimal Base Build-Schedule
&lt;/h3&gt;

&lt;p&gt;Let's go to your minimal-baseimage job, the first in the hierarchy, which provides the minimal, but regularly patched, base-system for our infrastructure/middleware containers. &lt;/p&gt;

&lt;p&gt;Look for the following setting and schedule the job to run regularly, for example to run daily at 8:00 in the morning: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm1yfrra3dlf98a4my3l3.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm1yfrra3dlf98a4my3l3.PNG" alt="Set up Schedules for your Minimal-Base-Images"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A base-image probably isn't going to be patched every other minute, so a daily, or even weekly schedule would be just fine. &lt;/p&gt;

&lt;h3&gt;
  
  
  Update derived containers automatically
&lt;/h3&gt;

&lt;p&gt;Now how do we get our derived images to build as well, once the minimal base-image is updated?&lt;/p&gt;

&lt;p&gt;Like this, for example: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff3tch11hfknhcr9s6l9m.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff3tch11hfknhcr9s6l9m.PNG" alt="Set up a Dependency to your minimal base-images"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That setting would take care of updating our NGinX base-image, once the minimal-baseimage has been patched. Obviously only, if the build actually succeeds. &lt;/p&gt;

&lt;h3&gt;
  
  
  Create Deployables as often as possible
&lt;/h3&gt;

&lt;p&gt;For images that contain actual source, which would be our deployables, those should get built pretty frequently. &lt;br&gt;
We don't only want builds when the baseimage gets patched, but also when our codebase changes. So let's implement that. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fims5n60naprlgrdyqffr.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fims5n60naprlgrdyqffr.PNG" alt="Setup dependency to infrastructure images"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;here we have two triggers, the first makes sure our app gets updated once the patches ran down the chain and arrived on our nginx container. &lt;/p&gt;

&lt;p&gt;Additionally, we poll our git, to trigger a build for incoming git commits.&lt;br&gt;
This, as is usually the case with polling, works in the beginning, for small teams that don't push dozens of builds in 5 minutes. Depending on your circumstances this could already suffice. &lt;/p&gt;

&lt;p&gt;Teams with a high push frequency will probably end up with a build containing multiple commits, which is probabyly undesired for eventual testing stages (or the blame-game if the build breaks :P). &lt;/p&gt;

&lt;p&gt;If you want a build for every push, reliably, you will have to look at your git repo-tool and check if your tool maybe provides post-push webhooks. &lt;/p&gt;

&lt;p&gt;The set up Jenkins for that, the config could look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhrchr4qbkzmytkhzcu2f.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhrchr4qbkzmytkhzcu2f.PNG" alt="Set up a Webhook for reliable push-based builds"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This allows your job to be called via REST, if the specified token is provided.&lt;/p&gt;

&lt;p&gt;Maybe there even is a cool plugin for your existing toolset, it would actually shock me if there wasn't. Jenkins existing Plugin-Base is enormous, there are plugins for pretty much anything. &lt;/p&gt;

&lt;h3&gt;
  
  
  Cleanup
&lt;/h3&gt;

&lt;p&gt;After a short while, your Jenkins Host could look like this: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqoobllnl2etli2lagz8v.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqoobllnl2etli2lagz8v.PNG" alt="Regularly clean your Dockercache on your Build hosts"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should probably think about regularly cleaning up your Jenkins-Host via &lt;code&gt;docker system prune -af&lt;/code&gt; to save space. &lt;/p&gt;

&lt;p&gt;So we will do just that with another job:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;

&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;   
    &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s2"&gt;"docker system prune -af"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add your schedule to run daily, just like you did before, and you are set!&lt;/p&gt;

&lt;h3&gt;
  
  
  Finishing Line
&lt;/h3&gt;

&lt;p&gt;Congratulations, you have a completely independent build-pipeline for your images now. If you trigger the minimal-baseimage by hand, or they are triggered by their daily schedule, Jenkins should walk it's way all the way down to your awesome website-container and everything should be patched, pushed and ready to use! &lt;/p&gt;

&lt;p&gt;Next time we can take a look at kubernetes and how we can implement a CI with tekton instead of Jenkins, if there is interest for the topic.&lt;br&gt;
Or we could go and scan our images with anchore for CVEs.&lt;/p&gt;

&lt;p&gt;Feel free to wish for something that interests you :)&lt;/p&gt;

</description>
      <category>containers</category>
      <category>docker</category>
      <category>cloud</category>
      <category>automation</category>
    </item>
    <item>
      <title>The Dos und Don’ts of containers</title>
      <dc:creator>Raphael Habereder</dc:creator>
      <pubDate>Wed, 10 Jun 2020 07:17:08 +0000</pubDate>
      <link>https://dev.to/habereder/the-dos-und-don-ts-of-containers-5en</link>
      <guid>https://dev.to/habereder/the-dos-und-don-ts-of-containers-5en</guid>
      <description>&lt;p&gt;So I got a very nice question from a community member here, which I would like to pick up in this series. &lt;/p&gt;

&lt;p&gt;It was as follows: &lt;br&gt;
&lt;em&gt;I’m going from being a low-code developer to full-stack. &lt;br&gt;
I’m building my first web app and wondering how I go from ‘it runs on my machine’ to ‘ive got a multi-environment, self-healing, auto-scaling, well-oiled internet machine’. &lt;br&gt;
What would you say are the key tasks to achieve with containers and when should they be done in the project?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That is a fantastic question which merits a whole series of posts to be perfectly honest. The topic of containers might seem intimidating at first. &lt;br&gt;
Containers, Images, Operators, Orchestration-Tools, Ingresses, Observability, Monitoring, PaaS, CaaS, SaaS and many more buzzwords await you in the world of containers. &lt;br&gt;
But fear not, there are many essentials you can always keep in mind, that I will try to cover as much as possible in this series. &lt;br&gt;
These tasks/tips will get you into a great starting position, once you start tackling the topic of containers. &lt;/p&gt;

&lt;p&gt;Let's split that question up into smaller chunks first. &lt;br&gt;
We have the following three topics to talk about here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The key tasks to achieve with containers&lt;/li&gt;
&lt;li&gt;When they should be done&lt;/li&gt;
&lt;li&gt;The actual transition from bare-metal/manual deployment to containers&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  1) The key tasks to achieve with containers
&lt;/h2&gt;

&lt;p&gt;There are many benefits to containers, here is a small list of things my customers have said in the past, that were of "special importance to them". &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Save money by using hardware as optimally as possible&lt;/li&gt;
&lt;li&gt;Have software be more portable&lt;/li&gt;
&lt;li&gt;Improve speed and quality of development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think these reasons don't sound too bad. A lot can be already done, even without containers, but here is my take on it:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Save money by using hardware as optimally as possible&lt;/em&gt;&lt;br&gt;
Containers require less overhead than the typical VM-Landscape many companies have today. By removing the hypervisor and sharing the same kernel between all containers, you suddenly get a lot  more containers onto your hardware than VMs. &lt;br&gt;
Not only do you save overhead, you also gain in speed. While VMs are just as scalable as containers, horizontally scaling a simple apache2 container is quite a bit faster than spinning up preimaged VMs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have software be more portable&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Damn it, I forgot to install a JDK on Server X, now the microservice won't even start!&lt;/strong&gt;&lt;br&gt;
This won't happen with a container, since you package everything you need with your deliverable, the service. &lt;br&gt;
If your container runs locally, it will run on Azure, GCS, Amazon ECS, or an on-premise PaaS (except if something is majorly borked).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Improve speed and quality of development&lt;/em&gt;&lt;br&gt;
If you correctly set up your environments, a deployment to a different cluster/location/stage might just be a simple additional line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl config use-context aws
kubectl apply -f awesomeapp-deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want to deploy on azure instead?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl config use-context azure
kubectl apply -f awesomeapp-deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my opinion, it doesn't really get easier or faster than that. &lt;br&gt;
Quality, as always, is (probably) not to blame on the technology. &lt;/p&gt;
&lt;h2&gt;
  
  
  2) When they should be done
&lt;/h2&gt;

&lt;p&gt;This can be answered pretty quick, I would say &lt;strong&gt;as early as possible&lt;/strong&gt;. What I have learnt in many years of non-stop containerization is, "do it correct and right at the beginning". Go the extra mile, make your containers and surrounding infrastructure as seamless as possible. Don't skimp on things you think "might be overkill". &lt;br&gt;
Automation/Optimization is boring, sometimes ugly and very cumbersome. But try to do it anyways, so you can reap the rewards as early as possible. You can easily strip out functionality you may not need afterwards. But adding onto a pipeline at the end almost always ends up in chaos. &lt;/p&gt;
&lt;h2&gt;
  
  
  3) The actual transition
&lt;/h2&gt;

&lt;p&gt;What we want to achieve is, as was perfectly described before, "a well-oiled internet machine". So let's get started with the do's and don't's of containers with practical Dockerfiles. &lt;/p&gt;
&lt;h3&gt;
  
  
  Tip 1) Control
&lt;/h3&gt;

&lt;p&gt;Have a single base-image, all your other images derive from. If you need something on top of it, make another base-image that derives from that. Put your actual stuff another layer down in final Images. &lt;/p&gt;

&lt;p&gt;It could look like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;minimal-baseimage (for example ubuntu, alpine, centos)
|__ nginx-baseimage
|  |__ awesome-website-container
|__ quarkus-baseimage
|  |__ awesome-java-microservice
|__ python-baseimage
   |__ awesome-flask-webapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why would you do that? &lt;br&gt;
It's simple really, you need to update your images because of CVE's, patches or need something else in all your images? Update your top minimal-baseimage, kick off your CI and watch all your images getting updated to the same basic state. &lt;/p&gt;

&lt;p&gt;Let's take a look at the path minimal -&amp;gt; nginx -&amp;gt; awesome-website. &lt;/p&gt;

&lt;p&gt;This could be your Dockerfile for your minimal-baseimage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu:18.04&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nt"&gt;-y&lt;/span&gt; update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get &lt;span class="nt"&gt;-y&lt;/span&gt; upgrade &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get autoclean &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get clean 


&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /opt&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown &lt;/span&gt;nobody. /opt
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nobody&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This could be an image that installs nginx to host your webpage for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; my-ubuntu-base:18.04-somebuildnumber&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nginx &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get clean &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chown &lt;/span&gt;nobody. /usr/share/nginx/html
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nobody&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;
&lt;span class="k"&gt;STOPSIGNAL&lt;/span&gt;&lt;span class="s"&gt; SIGTERM&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, this would be the actual container you deploy your website with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; my-ubuntu-nginx-base:1.18.0-stable&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; awesomefiles/index.html /usr/share/nginx/html/index.html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This might look overkill, if you could replace the whole chain by doing something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; static-html-directory /usr/share/nginx/html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But what is the difference, except copious amounts of effort for a single nginx-image with an awesome html-file?&lt;/p&gt;

&lt;p&gt;Many things, but here are four important examples&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You know and control what is inside your container&lt;/li&gt;
&lt;li&gt;You can make sure it is updated and control the update process yourself&lt;/li&gt;
&lt;li&gt;You already got the prerequisites to fix anything when it's broken, or your security scanner complains about "outdated packages" (Which in  a commercial environment will happen a lot)&lt;/li&gt;
&lt;li&gt;You control the security context inside your container, by example with using nobody instead of root in the official nginx image&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tip 2) Simplicity
&lt;/h3&gt;

&lt;p&gt;As a rule of thumb, always keep in mind: "one function per container". &lt;br&gt;
While multiple processes in a single container are possible and in some edge-cases may be perfectly reasonable, it will increase dramatically in difficulty to manage the whole thing. &lt;/p&gt;

&lt;p&gt;Imagine the following processtree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@cc726267a502:/# pstree &lt;span class="nt"&gt;-ca&lt;/span&gt;
bash
  |-bash springboot.sh
  |   &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nt"&gt;-sleep&lt;/span&gt; 6000
  |-bash prometheus.sh
  |   &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nt"&gt;-sleep&lt;/span&gt; 6000
  |-bash grafana.sh
  |   &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nt"&gt;-sleep&lt;/span&gt; 6000
  |-bash h2.sh
  |   &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nt"&gt;-sleep&lt;/span&gt; 6000
  &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nt"&gt;-pstree&lt;/span&gt; &lt;span class="nt"&gt;-ca&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What would happen if prometheus died? Nothing really, and you wouldn't even know or get informed by docker. The main process, in this case some kind of bash wrapperscript, would still run. So Docker would have no reason to restart the container or identify it as broken. &lt;/p&gt;

&lt;p&gt;This would be the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@cc726267a502:/# &lt;span class="nb"&gt;kill &lt;/span&gt;347
root@cc726267a502:/# pstree &lt;span class="nt"&gt;-ca&lt;/span&gt;
bash
  |-bash springboot.sh
  |   &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nt"&gt;-sleep&lt;/span&gt; 6000
  |-bash grafana.sh
  |   &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nt"&gt;-sleep&lt;/span&gt; 6000
  |-bash h2.sh
  |   &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nt"&gt;-sleep&lt;/span&gt; 6000
  |-pstree &lt;span class="nt"&gt;-ca&lt;/span&gt;
  &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nt"&gt;-sleep&lt;/span&gt; 6000
&lt;span class="o"&gt;[&lt;/span&gt;2]   Terminated              bash prometheus.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you split all these processes into their own containers, your orchestration would notice if they died and spin them back up again. More on orchestration in another part, promised :)&lt;/p&gt;

&lt;p&gt;There are also other very practical reasons as for why you should limit yourself to a single process: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Scaling containers is much easier if the container is stripped down into a single function. You need another container with your app in it? Spin one up somewhere else. If your container contains all the other apps, this complicates things and maybe you don't even want a second grafana or prometheus&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Having a single function per container allows the container to be re-used for other projects or purposes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Debugging a simple container locally is way easier than pulling a gigantic god-container that was blown way out of proportions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Patching. Update your base-image, kick off your CI and you are good to go. Having to test for side-effects in other processes isn't fun &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Above also holds true for rollbacks of changes. The update bricked your app? No problem, change the tag in your deployment descriptor back to the old one, and you are finished. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I could go on about security, too, but let's leave it at that, you probably get the point. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important: I am not saying that multi-process containers are inherently bad. They can work and have their uses. As a beginner into containerization, I am just recommending you to keep it as simple as possible"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This tip won't come with a Dockerfile of the bad example really, since I don't want to encourage you starting the wrong way. Maybe later down the series we will tackle that process. &lt;/p&gt;

&lt;h3&gt;
  
  
  Tip 3) Updates
&lt;/h3&gt;

&lt;p&gt;As hinted towards in Tip 1) already, keep your containers updated. Always, regularly, automatically. Modern CI-Tools can help with that, most of them have webhook integrations for Git that can trigger your build-jobs. Or if you don't want to implement too much infrastructure at the start, build a cronjob or something. &lt;br&gt;
Just keep it automated. You should never have to patch a container yourself, because that increases the chance to just forget it. &lt;/p&gt;

&lt;h3&gt;
  
  
  Tip 4) Automation
&lt;/h3&gt;

&lt;p&gt;Automate as much as possible. You don't want to fumble around in a live container. Update the source, build, push, run your CI. Never touch a running container afterwards, or you are in for a world of pain if a live-container behaves different from a freshly built one and you don't know why. &lt;br&gt;
If you followed Tip 3), you already have a patch-pipeline in place, make use of that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tip 5) Safety
&lt;/h3&gt;

&lt;p&gt;Keep your container safe. The saying goes "nobody is perfect", which fits right into containers. User root is bad news. &lt;br&gt;
Your software should run as nobody, so a breach will get the intruder exactly nowhere, even if you forget to remap your usernamespace ids.&lt;/p&gt;

&lt;p&gt;Use base images with slimmed down containers. Your containers should contain no curl, no compilers, no ping or nslookup. Nothing that can result in changes or load to your or other peoples infrastructure if someone breaks in. &lt;/p&gt;

&lt;p&gt;Harden your runtime with best practices, remap your user namespace ids, scan your images regularly for vulnerabilities and keep privilege escalations like &lt;code&gt;USER root&lt;/code&gt; to a minimum. You know, the things you would do with a good server too. Containers should be treated like a possible Botnet member just as well.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Tip 6) Reliability
&lt;/h3&gt;

&lt;p&gt;This tip contains two things. Choose your base system wisely and stick to it as much as possible. For example &lt;strong&gt;alpine&lt;/strong&gt;. While alpine has drawbacks, and sometimes doesn't work with what you have planned to run on it, it does provide the following advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;super small footprint (6MB vs. Ubuntus 74MB or CentOS whopping 237MB)&lt;/li&gt;
&lt;li&gt;minimal attack surface from the outside, since alpine was designed with security in mind&lt;/li&gt;
&lt;li&gt;Alpine Linux is simple. It brings it's own package manager and works with the OpenRC init system and script driven set-ups. It just tries to stay out of your way as much as possible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Build your container yourself. While there might be 100 containers that already do what you want to get done, you have no idea how frequently they are patched, or what else is in them. So build the container of your dreams yourself. It helps you to get into the routine of hardening, patching and optimizing them. You can never get enough experience in that regard. Always keep in mind, "you build it, you run it, you are liable for it".  &lt;/p&gt;

&lt;h2&gt;
  
  
  Congrats you made it this far
&lt;/h2&gt;

&lt;p&gt;I believe these few simple tips will get you a good headstart in developing healthy containers. Next time, we will take a look at how a CI for your containers could look like, by using Jenkins or Tekton.&lt;/p&gt;

&lt;p&gt;If I left you with questions, as always, don't shy away from asking. There are no stupid questions, only stupid answers! &lt;/p&gt;

</description>
      <category>containers</category>
      <category>docker</category>
      <category>cloud</category>
      <category>automation</category>
    </item>
    <item>
      <title>An Introduction to Quarkus</title>
      <dc:creator>Raphael Habereder</dc:creator>
      <pubDate>Thu, 04 Jun 2020 22:08:16 +0000</pubDate>
      <link>https://dev.to/habereder/an-introduction-to-quarkus-4l4h</link>
      <guid>https://dev.to/habereder/an-introduction-to-quarkus-4l4h</guid>
      <description>&lt;h3&gt;
  
  
  The myth of slow Java
&lt;/h3&gt;

&lt;p&gt;In the realm of Java, who hasn't heard one of these statements at least a dozen times: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Java is slow"&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Java needs too much memory!"&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;"Java Services/Servers need too long to start!"&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;"Java isn't ready for microservices!"&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm glad to tell you, with this post, that none of these are true anymore. Actually, they haven't been true anymore for a long time.&lt;br&gt;
The last time I had a slow boot of an old-school Application Server was in the age of Tomcat or JBoss 4.x, and those are very, very old. &lt;/p&gt;
&lt;h3&gt;
  
  
  The solution to a non-existing problem
&lt;/h3&gt;

&lt;p&gt;The - hopefully final - nail in the coffin of these myths is named &lt;strong&gt;Quarkus&lt;/strong&gt;, or as the people behind this wonderful creation call it: "&lt;em&gt;Supersonic Subatomic Java&lt;/em&gt;".&lt;/p&gt;

&lt;p&gt;So what is it actually? Again, let's use the words of the creators: &lt;em&gt;"A Kubernetes Native Java stack tailored for OpenJDK HotSpot and GraalVM, crafted from the best of breed Java libraries and standards."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Alright, that doesn't really tell us much, it also introduces another Buzzword: &lt;em&gt;GraalVM&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;GraalVM? What is that now? &lt;/p&gt;

&lt;p&gt;Oracle created something wonderful with GraalVM, here is their sales-pitch on it: &lt;/p&gt;

&lt;p&gt;&lt;em&gt;"GraalVM is an ecosystem and shared runtime offering performance advantages not only to JVM-based languages such as Java, Scala, and Kotlin, but also to other programming languages such as JavaScript, Ruby, Python, and R. Additionally, it enables the execution of native code via an LLVM front-end, and WebAssembly programs on the JVM."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And let me tell you, those are not just empty promises! GraalVM is amazingly fast and combined with Quarkus, you are in for one hell of a joy-ride. &lt;/p&gt;
&lt;h3&gt;
  
  
  We want the numbers Mason!
&lt;/h3&gt;

&lt;p&gt;While evaluating Quarkus for a Microservice Landscape, we did our homework first and compared three different environments: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payara Micro + JVM&lt;/li&gt;
&lt;li&gt;Quarkus + JVM&lt;/li&gt;
&lt;li&gt;Quarkus Native Image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While Payara Micro is already considered "fast" and "small footprint", Quarkus and a Quarkus Native Image respectively, are considerably faster and even smaller in overhead.&lt;/p&gt;

&lt;p&gt;Don't believe me? I'll back those claims up in just a second.  &lt;/p&gt;

&lt;p&gt;Here are some pictures of a loadtest we created. The application was equipped with JAX-RS and JPA, targeting a (brutally oversized) in-memory database (so we can reduce the impact of database speed or, god forbid, slow network traffic). &lt;/p&gt;

&lt;p&gt;The test itself consisted of 5000 Requests in 5 concurrent threads and a minimal database payload (it was just a string tbh). &lt;/p&gt;

&lt;p&gt;All we monitored were CPU-Load, Time and Memory-Usage of the service image itself. &lt;/p&gt;

&lt;p&gt;It's not really a detailed or scientific test, but the numbers are sufficient enough to name a clear winner. &lt;/p&gt;

&lt;p&gt;Payara Micro with OpenJDK 11.0.6 (Provided by GraalVM 20.0 CE):&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8ch7cdy7pnnfn4w7x9rc.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8ch7cdy7pnnfn4w7x9rc.PNG" alt="Payara with GraalVM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Quarkus with OpenJDK 11.0.6 (Provided by GraalVM 20.0 CE):&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbuhkc5jqzrz1j7rungg0.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbuhkc5jqzrz1j7rungg0.PNG" alt="Quarkus with GraalVM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Quarkus Native Image:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fx0b032crw5pxp2syz92f.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fx0b032crw5pxp2syz92f.PNG" alt="Quarkus Native Image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Amazing how fast a native Image can be, right? &lt;/p&gt;

&lt;p&gt;As you can see, we got the following advantages, just by using quarkus native images: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;we reduced the time to first request from 7.3 seconds, to 0.104 seconds. This would typically include the costly bootstrapping of a jvm/application-server runtime, as well as the preparation of the JAX-RS Endpoint&lt;/li&gt;
&lt;li&gt;memory usage decreased from ~700 MByte to ~50 MByte &lt;/li&gt;
&lt;li&gt;the CPU-Load went from a wild rollercoaster of 50%-90% to consistent 30% during the whole lifecycle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While working off the 5000 Requests hasn't sped up as drastically, the memory consumption consistently stayed very low. &lt;/p&gt;

&lt;p&gt;If that isn't a noticeable improvement, I don't know what is. &lt;/p&gt;
&lt;h3&gt;
  
  
  So let's build something with Quarkus!
&lt;/h3&gt;

&lt;p&gt;A demo project, including the following Dockerfile can be found &lt;a href="https://github.com/RHabereder/quarkus-quickstart" rel="noopener noreferrer"&gt;here&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Are you ready to dive in? So here we go! &lt;/p&gt;

&lt;p&gt;First a basic Dockerfile to build some Java stuff&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;quay.io/quarkus/centos-quarkus-maven:19.3.1-java11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="c"&gt;# Since JVM has not been ported to alpines musl yet&lt;/span&gt;
&lt;span class="c"&gt;# and quarkus still relies on gcc for native binaries&lt;/span&gt;
&lt;span class="c"&gt;# We'll use the quarkus maven image as build-base&lt;/span&gt;


&lt;span class="c"&gt;# Lots of workarounds and setup for graalvm and quarkus to build the native binary&lt;/span&gt;
&lt;span class="c"&gt;# thankfully none of them end up in the final image&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; quarkus /usr/src/app
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; quarkus&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=quarkus app/ .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;./mvnw clean package &lt;span class="nt"&gt;-Pnative&lt;/span&gt;


&lt;span class="c"&gt;# Due to the very same reason of musl not playing along just yet&lt;/span&gt;
&lt;span class="c"&gt;# we will use the redhat minimal image for delivery&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; registry.access.redhat.com/ubi8/ubi-minimal&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="c"&gt;# Since the user should always be nobody (imho) and the USER-Directive only affects RUN, CMD and ENTRYPOINT &lt;/span&gt;
&lt;span class="c"&gt;# But not WORKDIR, we have to modify ownership of the workdir&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown &lt;/span&gt;nobody. /app

&lt;span class="c"&gt;# Copy as nobody&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build --chown=nobody /usr/src/app/target/*-runner /app/quarkusapp&lt;/span&gt;

&lt;span class="c"&gt;# Set up permissions for user `nobody`&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;775 /app &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s2"&gt;"g+rwX"&lt;/span&gt; /app &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; nobody. /app

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nobody&lt;/span&gt;

&lt;span class="c"&gt;# Tell quarkus to listen on all interfaces, instead of localhost&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["./quarkusapp", "-Dquarkus.http.host=0.0.0.0"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This handles the build part for you. &lt;/p&gt;

&lt;p&gt;You might be asking yourself now "Yeah, this is nice and all. But what if I need more than just your lame little JAX-RS Service? This doesn't really help me much..."&lt;/p&gt;

&lt;p&gt;Don't worry friend, quarkus has a &lt;a href="https://code.quarkus.io/" rel="noopener noreferrer"&gt;bootstrapping service&lt;/a&gt;, like most other cool cloud-ready frameworks today, where you can check some boxes and get the skeleton project ready to use!&lt;/p&gt;

&lt;p&gt;All this does basically, is change the dependencies in the resulting pom.xml, which the quarkus-maven-plugin picks up and builds your image with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;io.quarkus&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;quarkus-resteasy&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;io.quarkus&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;quarkus-junit5&amp;lt;/artifactId&amp;gt;
      &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;io.rest-assured&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;rest-assured&amp;lt;/artifactId&amp;gt;
      &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;io.quarkus&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;quarkus-resteasy-jsonb&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this short, but hopefully sweet, little introduction, you should be ready to go and quarkus(ify?) your applications. And don't forget, always try to have fun doing it!&lt;/p&gt;

&lt;p&gt;If I missed anything or you need some further information, don't shy away from leaving a comment, I'll make sure to leave none of you hanging :)&lt;/p&gt;

</description>
      <category>java</category>
      <category>docker</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Zero To Kubernetes</title>
      <dc:creator>Raphael Habereder</dc:creator>
      <pubDate>Tue, 26 May 2020 13:16:00 +0000</pubDate>
      <link>https://dev.to/habereder/zero-to-kubernetes-nd8</link>
      <guid>https://dev.to/habereder/zero-to-kubernetes-nd8</guid>
      <description>&lt;h3&gt;
  
  
  Elevator-Pitch
&lt;/h3&gt;

&lt;p&gt;Who doesn't love kubernetes? &lt;br&gt;
Who also doesn't love, if things just work without much tinkering, &lt;strong&gt;automagically&lt;/strong&gt;?&lt;br&gt;
If you do like both of these things, this might be a post for you!&lt;/p&gt;

&lt;p&gt;While I like using k8s, I dislike the part about cleaning stuff up after long sessions of deploying dozens of containers.&lt;br&gt;
Only to recreate the cluster again later. And damn it, I forgot to deploy grafana again... &lt;/p&gt;

&lt;p&gt;So I automated the setup/teardown of this, using a few handy tools and some Bash Scripting to incorporate more laziness in my workflow. &lt;/p&gt;
&lt;h3&gt;
  
  
  Grand Reveal
&lt;/h3&gt;

&lt;p&gt;I call it &lt;a href="https://github.com/RHabereder/zero-to-k8s"&gt;zero-to-k8s&lt;/a&gt;, or if you want a niftier name: &lt;strong&gt;02k8s&lt;/strong&gt;&lt;br&gt;
Which is totally readable and everyone instantly understands what it means. Right? &lt;strong&gt;Right?!&lt;/strong&gt;&lt;br&gt;
I better leave the title as it is, I think. &lt;/p&gt;
&lt;h3&gt;
  
  
  So, what is it?
&lt;/h3&gt;

&lt;p&gt;Essentially it is a glorified bash-script that downloads the typical binaries you need, for example kubectl, helm, k3d and the likes. &lt;br&gt;
Everything you need to use kubernetes and a little more for comfort. &lt;/p&gt;

&lt;p&gt;It then calls all those binaries, creates a k8s cluster and also deploys some workloads of tools you decided are worthy. &lt;br&gt;
Deployment, Service, Ingress and all the other nice things you need for them. &lt;br&gt;
Just one kubectl port-forward away from displaying in your favorite browser (as long as they are not w3m or lynx)&lt;/p&gt;
&lt;h3&gt;
  
  
  Where the laziness comes into play
&lt;/h3&gt;

&lt;p&gt;There are two versions of the script, one having to be edited every time you want something to change.&lt;br&gt;
And second, my favorite version (the lazy one) that pops up some dialogs and asks you annoying questions, so you don't have to do annoying editing every time. &lt;br&gt;
Unfortunately we have to die one annoying death, since my mind-reading interface is not done yet. &lt;/p&gt;
&lt;h3&gt;
  
  
  Awesome Demo
&lt;/h3&gt;

&lt;p&gt;Why don't I just give you the grand tour of how it works?&lt;br&gt;
Behold the blue curses widget goodness:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gCjKtOHy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/x1qgrbca28xz57iw8mmp.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gCjKtOHy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/x1qgrbca28xz57iw8mmp.gif" alt="Demo GIF"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The things I used:
&lt;/h3&gt;

&lt;p&gt;k3s - A stripped down kubernetes runtime developed by rancher, or as they call it "a highly available, certified Kubernetes distribution designed for production workloads in unattended, resource-constrained, remote locations or inside IoT appliances" &lt;br&gt;
k3d - Another tool made by rancher to deploy k3s inside docker. &lt;/p&gt;
&lt;h3&gt;
  
  
  The things you need:
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WSL/Bash/MacOS ZSH (bless you if you get to work on a real Linux)
Docker (the whole "k3s in docker" approach probably won't work without it) 
Dialog (if you want to be as lazy as possible)
Access to the internetz (Or a very patient IT-Sec Department that downloads stuff for you)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  I don't like it, make it go away!
&lt;/h3&gt;

&lt;p&gt;To kill your cluster and thanos-snap it away (50% of the time it works all the time, or so I've been told), all you need is one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./bin/k3d delete --name dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No harm, no foul, you do you my friend. I promise I won't take it personally!&lt;br&gt;
As every good technician, the entire blame is on the tools, obviously.&lt;/p&gt;

&lt;h3&gt;
  
  
  What do I want to do with this thing?
&lt;/h3&gt;

&lt;p&gt;To be honest, all I want is to continue to automate as much as possible, to enjoy a coffee during all the boring tasks that just take unnecessary amounts of time. &lt;br&gt;
And learn of new tools and probably automate them away too. &lt;br&gt;
So if you got something that just has to go in there, don't shy away from shooting me a message.&lt;/p&gt;

&lt;p&gt;I hope some of you find this as helpful as I do, let's go and automate the rest of the world!&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>automation</category>
      <category>devops</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
