<?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: Dominik Weber</title>
    <description>The latest articles on DEV Community by Dominik Weber (@domysee).</description>
    <link>https://dev.to/domysee</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%2F38080%2Feefdbcc1-0480-4791-a44d-2d927e3fb923.JPG</url>
      <title>DEV Community: Dominik Weber</title>
      <link>https://dev.to/domysee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/domysee"/>
    <language>en</language>
    <item>
      <title>Developing a reading list for programmers - Motivation</title>
      <dc:creator>Dominik Weber</dc:creator>
      <pubDate>Mon, 22 Mar 2021 08:27:37 +0000</pubDate>
      <link>https://dev.to/domysee/developing-an-information-hub-motivation-1ffp</link>
      <guid>https://dev.to/domysee/developing-an-information-hub-motivation-1ffp</guid>
      <description>&lt;p&gt;A few months ago, the reading list of my choice, Pocket, made a change that I didn't agree with. A minor change, but nonetheless extremely annoying.&lt;br&gt;&lt;br&gt;
That sparked a thought. I love working on side-projects, why not develop my own?&lt;/p&gt;

&lt;p&gt;Following that decision, I started thinking of other features a reading list could have.&lt;br&gt;&lt;br&gt;
The ideas ranged from simple, like showing if an article was already read (in case it's added a second time), to complex, like keeping a backup of the website in case it goes down or the article is removed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grand vision(s)
&lt;/h2&gt;

&lt;p&gt;After a few weeks the idea expanded. It doesn't have to be a reading list only. It could include any kind of information that can be linked to. Books, Podcasts, online courses, videos, lectures.&lt;br&gt;&lt;br&gt;
Such a product could become a &lt;strong&gt;central hub of information&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Not only listing, but organizing it as well. Features like showing the source of an article, who recommended the article, book or video, adding notes, following specific high-quality blogs, and many more crept into my mind.&lt;/p&gt;

&lt;p&gt;I use a reading list because I often come across great content, but don't have the time or energy to consume it in that moment. So I add it to the list for later.&lt;br&gt;&lt;br&gt;
That's the idea of a reading list. But it could be so much more...&lt;/p&gt;

&lt;p&gt;What if I didn't view it as only a list of information, but as a &lt;strong&gt;central place to manage information &lt;em&gt;consumption&lt;/em&gt;.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The difference being that it doesn't just list, it also helps to &lt;em&gt;retain&lt;/em&gt; the knowledge that these articles contain. In this area I'm still a little short in what I could do. Spaced repetition is one thing, but I'm sure there's a lot more possible.&lt;/p&gt;

&lt;p&gt;As you can see, this little project evolved into something huge. Only in thought for now, it'll probably take years until I have realized that vision. But every great thing starts at the beginning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Opportunity to tackle societal problems?
&lt;/h3&gt;

&lt;p&gt;Not being short of visions, I found two more that are important to me and I might be able to do with this project.  &lt;/p&gt;

&lt;p&gt;The first is battling fake and false news. The difference I make for this article is that fake news are deliberately presented as true, while false news is simply a mistake.&lt;br&gt;&lt;br&gt;
For false news I already have an idea. Articles are often corrected, and usually there's then a correction section at the end. But very few people check back to see if there was a correction. So the feature could be to automatically watch changes on articles, and if a correction appears, show it.&lt;br&gt;&lt;br&gt;
I have no idea yet what to do against fake news though.&lt;/p&gt;

&lt;p&gt;The second is spearheading a move to higher quality information.&lt;br&gt;&lt;br&gt;
In the past years, with the commodization of content publishing, a lot more high- and low-quality content became available, but the ratio shifted towards low-quality.&lt;br&gt;&lt;br&gt;
It also seems to me that searching for specific topics doesn't list the high quality information. What I usually get is listicles or short articles about the topic. Long, well-researched writups are rarely found.&lt;br&gt;&lt;br&gt;
I have an idea of how to tackle this, but it's not refined enough yet to share. I can only say that for it to happen I need users. Many users. Which means I'll create a valuable product first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development
&lt;/h2&gt;

&lt;p&gt;Rome wasn't built in a day, and it'll be the same with my little project.&lt;/p&gt;

&lt;p&gt;The first step is to build a basic reading list. Then I'll add more and more features to improve the product and serve the vision.&lt;/p&gt;

&lt;p&gt;A first version is done already. I named it ReadingDragon and you can find it here: &lt;a href="https://www.reading-dragon.com/"&gt;https://www.reading-dragon.com/&lt;/a&gt;&lt;br&gt;&lt;br&gt;
It is in alpha now. You can use the code 'alpha' to get 66% discount. Or 'free' if you don't share my vision, then you can try it out for free.&lt;/p&gt;

&lt;p&gt;Since it's a side-project, and software development is my passion, I'll also share my progress on &lt;a href="https://twitter.com/Domysee"&gt;Twitter&lt;/a&gt; and via articles like these (which I'll also share on Twitter).&lt;br&gt;&lt;br&gt;
I'll blog about product evolvement and about technical aspects, like the architecture, testing strategy and so on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The whole idea developed from a small and simple reading list to a really ambitious project. It is a bit intimidating, but I like the fact that I can potentially work on it for years to come.&lt;/p&gt;

&lt;p&gt;Wish me luck! :) &lt;/p&gt;

</description>
      <category>productivity</category>
      <category>showdev</category>
      <category>development</category>
    </item>
    <item>
      <title>Thoughts on Developer Autonomy</title>
      <dc:creator>Dominik Weber</dc:creator>
      <pubDate>Fri, 06 Dec 2019 05:33:14 +0000</pubDate>
      <link>https://dev.to/domysee/thoughts-on-developer-autonomy-50oa</link>
      <guid>https://dev.to/domysee/thoughts-on-developer-autonomy-50oa</guid>
      <description>&lt;p&gt;The level of autonomy developers get influences their learning speed, job satisfaction and how fast teams can deliver software. As a person seeking as much freedom as possible this is a topic dear to my heart.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: I write as if all of what I'm saying is true. But this is just my writing style. It's still only my current thoughts on this topic.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I have seen a few different levels of planning out there. &lt;/p&gt;

&lt;p&gt;1) People are told what task to work on&lt;br&gt;
2) People can choose tasks from a predefined list&lt;br&gt;
3) People get the vision, discuss how to best fulfill it, and then execute on that&lt;br&gt;
4) People get the vision and work on whatever they want to fulfill it&lt;br&gt;
5) People work on whatever they want&lt;/p&gt;

&lt;p&gt;I personally experienced the first 3, heard first hand about the 4th one. The last one is just there for completeness sake, I have actually never heard or read from anyone having such a job, but I guess they exist &lt;em&gt;somewhere&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In general autonomy is lowest in the first one and goes up the further you go down the list. There's another axis impacting freedom though, &lt;strong&gt;task size&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If people are told exactly which task to work on (1), but they are huge, say 2 weeks of work, they are more autonomous than in a system where it's discussed how to best fulfill the vision (3), but a sync and evaluation happens every day.&lt;/p&gt;

&lt;p&gt;As soon as a task is estimated, team leads and managers want to keep close to that to not mess up their plans for the team and projects. Going on a tangent to future-proof code which doubles the task size is often not desired.&lt;/p&gt;

&lt;p&gt;With the estimated time being at least somewhat fixed, doing their own estimates gives engineers the ability to calculate in important adjacent activities (like code improvements) that others might overlook. If team leads do the estimate all on their own, this freedom is lost.&lt;/p&gt;

&lt;p&gt;The worst in terms of freedom is a team where the team lead estimates tasks, these tasks are tiny and people are told exactly what to work on at which time. And to top it all off, it's even defined exactly &lt;em&gt;how&lt;/em&gt; to do them. Micromanagement in its purest form.&lt;/p&gt;

&lt;p&gt;Jokes aside, I didn't hear of any organization yet where it's that bad.&lt;/p&gt;

&lt;h2&gt;
  
  
  Impact on Developers
&lt;/h2&gt;

&lt;p&gt;Particularly young developers profit enormously from increased levels of autonomy. It provides a wider area for mistakes. And the mistakes made are their own. Which is crucial for learning.&lt;/p&gt;

&lt;p&gt;If the someone else decided how to do it, it's too easy to reject all responsibility. In this case the learning effect is weak. &lt;/p&gt;

&lt;p&gt;This is also why side-projects are so powerful. Developing one entirely on your own means you have total control, all decision are yours and all mistakes too. You see the impact of everything you do, of every line of code you write.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is not to say that you can hire people directly out of university, let them alone for 2 years, and suddenly they are the best developers in the world. No. Inexperienced engineers need guidance and support. Sometimes there are issues they just can't figure out, or a quick explanation saves them days of research. It's equally important to show them what to learn, so they invest their time effectively, and to help them achieve their best.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For senior developers autonomy is equally important, just in a different way. They already know how to program and write software. To improve they need a wider area of impact, get exposed to more and different technologies, and learn the "softer" aspects of software engineering. Not only soft skills, also how their choices impact the consumer, how to create a good architecture, which technologies to use, how to fulfill requirements without overengineering, so on so forth.&lt;/p&gt;

&lt;p&gt;I'd say that in general, people strive for autonomy and freedom in their lives. Developers maybe even more so than the average person. Regardless of how much experience an engineer has, autonomy will impact their job satisfaction. Exactly how much is different for each person, also because more autonomy usually also means more responsibility. This is for their team leads to figure out. Overall I'd say though the more the better for employee happiness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Impact on Team Leads?
&lt;/h2&gt;

&lt;p&gt;Team leads are responsible for the team's speed and set, or at least have a strong influence on, all of the 3 axis of autonomy I described above (task size, definition and estimation). The way this is done not only affects the developers, but also the team lead.&lt;/p&gt;

&lt;p&gt;The more detail a task description has the better, as long as it describes what to do. Team leads often got their position because they are great developers and therefore usually have an idea on how to achieve the desired result. For the sake of team efficiency and velocity they add their knowledge to the description.&lt;/p&gt;

&lt;p&gt;For issues where speed or correctness are highly important this makes sense, but that are by far not all. For the others detailed descriptions about what to do just has the potential to reduce the lead's productivity. If there is a pointer of &lt;em&gt;how&lt;/em&gt; to implement something, questions for more details will come, which sporadically interrupt both parties. When enough of that happens it becomes difficult for the team lead to focus on bigger tasks.&lt;/p&gt;

&lt;p&gt;The increased time needed to do closer managing of developers (not just answering questions) reduces the size the team can have. It's possible to micromanage 3 people, with more macro management it can go up to 10 or maybe even more. So higher levels of autonomy save time.&lt;/p&gt;

&lt;p&gt;The goal is usually not to reduce the autonomy of developers. How much they have is usually a side-effect of everything else they do, often from measures to increase the team's development speed or make planning more accurate. For example splitting tasks up because small ones can be estimated more accurately or exactly knowing how to achieve the desired result to reduce estimation variance. &lt;/p&gt;

&lt;p&gt;Over the long run increased autonomy engages the engineers and uses their full potential, knowledge and creativity for solving problems. As mentioned above, this maximizes skill improvement and therefore increases the team's velocity.&lt;/p&gt;

&lt;p&gt;All of that is justified by developers learning faster with more autonomy. I'm quite certain that this is true, but of course cannot be 100%. It might also be different for each person. Full autonomy doesn't mean no guidance or mentoring though. This can and should still happen. It could even have more impact in high-autonomy environments. &lt;/p&gt;

&lt;p&gt;Actively giving more freedom is not always an easy choice. It means not always knowing the status of tasks, where difficulties are and when they'll be done. It makes coordination and future planning difficult. Also reporting is affected by that. &lt;/p&gt;

&lt;p&gt;So currently I'm thinking that high autonomy makes the team lead's job is more difficult with the long-term reward of a faster team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The topic of autonomy is a hard one. Most of the time it's a side-effect of other measures and not explicitly thought about.&lt;/p&gt;

&lt;p&gt;My current view is that autonomy is the key to being able to manage more and more people, utilizing their full potential and keeping developers engaged. The exact way to achieve all that depends on the company, the environment, the team lead and the people making up the team.&lt;/p&gt;

&lt;p&gt;There is, as always, but unfortunately, not a one size fits all solution.&lt;/p&gt;

</description>
      <category>motivation</category>
      <category>productivity</category>
      <category>psychology</category>
    </item>
    <item>
      <title>Type-Safe Redux Reducers</title>
      <dc:creator>Dominik Weber</dc:creator>
      <pubDate>Fri, 23 Aug 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/domysee/type-safe-redux-reducers-3o5i</link>
      <guid>https://dev.to/domysee/type-safe-redux-reducers-3o5i</guid>
      <description>&lt;p&gt;The main problem that has to be solved is how to show TypeScript what the concrete action type is after checking the &lt;code&gt;type&lt;/code&gt; property of Redux actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Straightforward Solution
&lt;/h2&gt;

&lt;p&gt;The most straightforward way to do this is creating a type for every action and using &lt;a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types"&gt;type guards&lt;/a&gt; to distinguish them.&lt;/p&gt;

&lt;p&gt;Type guards are boolean functions that check if a parameter is a specific type, indicated by the return value being &lt;code&gt;parameter is Type&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Used in a condition, TS assumes the variable passed to the type guard is of the type the guard checks for. Only in the true branch of course.&lt;/p&gt;

&lt;p&gt;An example solution with type guards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;StartAction&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SuccessAction&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ErrorAction&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createStart&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;&lt;/span&gt; &lt;span class="nx"&gt;StartAction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createSuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;SuccessAction&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ErrorAction&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;error&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// TYPE GUARDS&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isStartAction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;StartAction&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
  &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isSuccessAction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;SuccessAction&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
  &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isErrorAction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;ErrorAction&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
  &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;lastStarted&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;defaultState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;lastStarted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reducer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;defaultState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isStartAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;lastStarted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isSuccessAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isErrorAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&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;This works, but is quite verbose. &lt;/p&gt;

&lt;p&gt;For every action, it's necessary to define&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The action type&lt;/li&gt;
&lt;li&gt;The action creator&lt;/li&gt;
&lt;li&gt;The type guard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And whenever an action changes, a property is added/removed/changed, it has to be adapted on 2 places, the specific action interface and the action creator.&lt;/p&gt;

&lt;p&gt;It's possible to get rid of both, but that requires a little detour first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inferring Concrete Types from Unions
&lt;/h2&gt;

&lt;p&gt;TS has a neat type inference feature. Under specific circumstances, it can infer the concrete type from a union. What I mean is this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;someObject&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;someCondition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// x is of type A here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this to work &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;B&lt;/code&gt; must have a property in common. In Redux actions it would be &lt;code&gt;type&lt;/code&gt;. This is necessary so that TS knows this property can be accessed in the condition.&lt;/p&gt;

&lt;p&gt;Additionally the &lt;code&gt;type&lt;/code&gt; property must be a literal type, for example a literal string (e.g. &lt;code&gt;'Start'&lt;/code&gt;) or number (e.g. &lt;code&gt;123&lt;/code&gt;).&lt;br&gt;&lt;br&gt;
This type is checked in the condition. Since each type's &lt;code&gt;type&lt;/code&gt; property can only be a specific value, TS can infer the type based on the given value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;propA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;B&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;End&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;propB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// x is of type A here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this reason it's necessary that the &lt;code&gt;type&lt;/code&gt; properties are literal types. If they were a generic &lt;code&gt;string&lt;/code&gt;, there'd be nothing to go on for type inference. &lt;/p&gt;

&lt;h2&gt;
  
  
  Using Unions to Infer the Action Type
&lt;/h2&gt;

&lt;p&gt;Armed with that knowledge, it's possible to get rid of the type guards, in exchange for adding &lt;code&gt;ActionTypes&lt;/code&gt;, which is a union of all action types handled in that reducer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;StartAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SuccessAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ErrorAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ActionTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;StartAction&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;SuccessAction&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;ErrorAction&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;StartAction&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createSuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;SuccessAction&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ErrorAction&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;error&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;lastStarted&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;defaultState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;defaultState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ActionTypes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Start&lt;/span&gt;&lt;span class="dl"&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;lastStarted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success&lt;/span&gt;&lt;span class="dl"&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error&lt;/span&gt;&lt;span class="dl"&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&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;h2&gt;
  
  
  Removing Action Types with &lt;code&gt;ReturnType&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The last improvement to arrive at the final example is automatically inferring action types based on the return value of action creators.&lt;/p&gt;

&lt;p&gt;This can be done with the helper type &lt;code&gt;ReturnType&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
As the name says, it is a generic type that, if passed a function, returns the type of the returned value. For example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;F&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;F&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// T = boolean&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;If you're interested how it works, check out my other blogpost &lt;a href="https://www.domysee.com/blogposts/ts-builtin-types-list"&gt;List of Built-In Helper Types in TypeScript&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;By using that helper type it's possible to create the union type directly from the return types of the action creators.&lt;/p&gt;

&lt;p&gt;One thing to notice here is that the returned objects define the &lt;code&gt;type&lt;/code&gt; like this: &lt;code&gt;'Start' as 'Start'&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
This is so the inferred type is of the literal string (&lt;code&gt;'Start'&lt;/code&gt;), because by default, when assigning a string, TS infers it to be of type &lt;code&gt;string&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ActionTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;createStart&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;createSuccess&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;createError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createSuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;error&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;lastStarted&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;defaultState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;defaultState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ActionTypes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Start&lt;/span&gt;&lt;span class="dl"&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;lastStarted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success&lt;/span&gt;&lt;span class="dl"&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error&lt;/span&gt;&lt;span class="dl"&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&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;This is the final example, as concise and with the least typing effort possible.&lt;/p&gt;

&lt;p&gt;All solutions are valid though, and depending on the rest of the codebase, the team and your preference you might want to choose a more explicit (first) way to implement type-safe reducers.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>redux</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Benefits of a Component Library</title>
      <dc:creator>Dominik Weber</dc:creator>
      <pubDate>Sun, 09 Jun 2019 09:03:36 +0000</pubDate>
      <link>https://dev.to/domysee/benefits-of-a-component-library-2baa</link>
      <guid>https://dev.to/domysee/benefits-of-a-component-library-2baa</guid>
      <description>&lt;p&gt;At my work we're currently starting to create a company-internal component library.&lt;br&gt;&lt;br&gt;
It will be a bit of work, but significantly improve our UI development.&lt;/p&gt;

&lt;p&gt;The most obvious benefits are:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduced code duplication&lt;/li&gt;
&lt;li&gt;Increased speed&lt;/li&gt;
&lt;li&gt;Improved UI consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One benefits that is less obvious, but underpins all those is &lt;strong&gt;reduced decision making effort&lt;/strong&gt;. For designers, developers and the interaction of those groups.&lt;/p&gt;

&lt;p&gt;In my opinion a component library is there to limit the possible designs, not just to have some base components.&lt;br&gt;&lt;br&gt;
And with limited possibilities, the thinking process will be faster. Instead of literally endless options, designers only have a few. The same thing goes for developers. They see the design and know which components to use. Overriding styles is not an option, only if it is explicitly mentioned and approved by all stakeholders. &lt;/p&gt;

&lt;p&gt;If you're not thinking about component libraries as a way to limit design possibilities, you're only getting half the benefit.&lt;br&gt;&lt;br&gt;
The logic will be abstracted, yes, but the styling will still often be overwritten, which might even need custom logic at some point.&lt;/p&gt;

&lt;p&gt;This is how I currently believe we can get the most of our component library.&lt;/p&gt;

&lt;p&gt;What is your opinion? Do you think it makes sense? Maybe it would be too limiting for a designer? That it'd hamper their creativity? Or even facilitate it?&lt;br&gt;&lt;br&gt;
Do you know of any other benefits component libraries have? &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>discuss</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Setting up a Reverse-Proxy with Nginx and docker-compose</title>
      <dc:creator>Dominik Weber</dc:creator>
      <pubDate>Mon, 23 Jul 2018 12:56:22 +0000</pubDate>
      <link>https://dev.to/domysee/setting-up-a-reverse-proxy-with-nginx-and-docker-compose-29jg</link>
      <guid>https://dev.to/domysee/setting-up-a-reverse-proxy-with-nginx-and-docker-compose-29jg</guid>
      <description>&lt;p&gt;Nginx is a great piece of software that allows you to easily wrap your application inside a reverse-proxy, which can then handle server-related aspects, like SSL and caching, completely transparent to the application behind it.&lt;/p&gt;

&lt;p&gt;This is a cross-post from my &lt;a href="http://www.domysee.com/blogposts/blogpost%2020%20-%20setting%20up%20a%20reverse-proxy%20with%20nginx%20and%20docker-compose/"&gt;personal website&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Some aspects of web applications, like SSL encryption, request caching and service discovery can be managed outside of the application itself. Reverse-proxies like Nginx can handle many of those responsibilities, so we as developers don't have to think about it in our software.&lt;/p&gt;

&lt;p&gt;Additionally, some software is not meant to be available over the internet, since the don't have proper security measures in place. Many databases are like that. And it is good practice in general to not make internal services public-facing that don't have to be.&lt;/p&gt;

&lt;p&gt;All of that can be achieved with docker-compose and Nginx.&lt;/p&gt;

&lt;h2&gt;
  
  
  docker-compose
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.docker.com/compose/"&gt;docker-compose&lt;/a&gt; is a neat little tool that lets you define a range of docker containers that should be started at the same time, and the configuration they should be started with. This includes the exported ports, the networks they belong to, the volumes mapped to it, the environment variables, and everything else that can be configured with the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;In this section I'll briefly explain how to configure the docker-compose features used in this article. For more details take a look at the &lt;a href="https://docs.docker.com/compose/compose-file/"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The main entry point is a &lt;code&gt;docker-compose.yml&lt;/code&gt; file. It configures all aspects of the containers that should be started together.&lt;/p&gt;

&lt;p&gt;Here is an example &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&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;nginx:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production_nginx&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx.conf:/etc/nginx/nginx.conf&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="s"&gt;80:80&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;443:443&lt;/span&gt;

  &lt;span class="na"&gt;ismydependencysafe&lt;/span&gt;&lt;span class="pi"&gt;:&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;ismydependencysafe:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production_ismydependencysafe&lt;/span&gt;
    &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see, there are 2 images specified.&lt;br&gt;&lt;br&gt;
First &lt;code&gt;nginx&lt;/code&gt;, with the name &lt;code&gt;production_nginx&lt;/code&gt;. It specifies a volume that replaces the default Nginx configuration file. Also a mapping of the host's ports 80 and 443 to the container's ports 80 and 443 is defined.&lt;br&gt;&lt;br&gt;
The second image is one is one I created myself. It exposes port 80. The difference to the &lt;code&gt;ports&lt;/code&gt; configuration is that they are not published to the host machine. That's why it can also specify port 80, even though &lt;code&gt;nginx&lt;/code&gt; already did.&lt;/p&gt;

&lt;p&gt;There are a few other configuration options used in this article, specifically networks, volumes and environment variables.&lt;/p&gt;
&lt;h3&gt;
  
  
  Networks
&lt;/h3&gt;

&lt;p&gt;With networks it is possible to specific which containers can talk to each other. They are specified as a new root config entry and on the container configurations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3'
services:
  nginx:
    ...
    networks:
      - my-network-name

  ismydependencysafe:
    ...
    networks:
      - my-network-name

networks:
  my-network-name:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the root object &lt;code&gt;networks&lt;/code&gt;, the network &lt;code&gt;my-network-name&lt;/code&gt; is defined. Each container is assigned to that network by adding it to the &lt;code&gt;network&lt;/code&gt; list.&lt;/p&gt;

&lt;p&gt;If no network is specified, all containers are in the same network, which is created by default. Therefore, if only one network is used, no network has to be specified at all.&lt;/p&gt;

&lt;p&gt;A convenient feature of networks is that containers in the same one can reference each other by name. In the example above, the url &lt;code&gt;http://ismydependencysafe&lt;/code&gt; will resolve to the container &lt;code&gt;ismydependencysafe&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Volumes
&lt;/h3&gt;

&lt;p&gt;Volumes define persistent storage for docker containers. If an application writes somewhere no volume is defined, that data will be lost when the container stops.&lt;/p&gt;

&lt;p&gt;There are 2 types of volumes. The ones that map a file or directory to one inside the container, and the ones that just make a file or directory persistent (named volumes), without making them accessible on the file system (of course they are &lt;em&gt;somewhere&lt;/em&gt;, but that is docker implementation specific and should not be meddled with).&lt;/p&gt;

&lt;p&gt;The first type, volumes that map a specific file or directory into the container, we have already seen in the example above. Here is it again, with an additional volume that also specifies a directory in the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3'
services:
  nginx: 
    image: nginx:latest
    container_name: production_nginx
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - /etc/letsencrypt/:/etc/letsencrypt/
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Named volumes are specified similar to networks, as a separate root configuration entry and directly on the container configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3'
services:
  nginx:
    ...
    volumes:
      - "certificates:/etc/letsencrypt/"

    ...

volumes:
  certificates:
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Environment Variables
&lt;/h3&gt;

&lt;p&gt;Docker can also specify environment variables for the application in the container. In the compose config, there are multiple ways to do so, either by specifying a file that contains them, or declaring them directly in &lt;code&gt;docker-compose.yml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3'
services:
  nginx:
    ...
    env_file:
      - ./common.env
    environment:
      - ENV=development
      - APPLICATION_URL=http://ismydependencysafe
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see, both ways can also be used at the same time. Just be aware that variables set in &lt;code&gt;environment&lt;/code&gt; overwrite the ones loaded from the files.&lt;/p&gt;

&lt;p&gt;The environment files must have the format &lt;code&gt;VAR=VAL&lt;/code&gt;, one variable on each line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ENV=production
APPLICATION_URL=http://ismydependencysafe
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  CLI
&lt;/h3&gt;

&lt;p&gt;The commands for starting and stopping the containers are pretty simple.&lt;/p&gt;

&lt;p&gt;To start use &lt;code&gt;docker-compose up -d&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
The &lt;code&gt;-d&lt;/code&gt; specifies that it should be started in the background. Without it, the containers would be stopped when the command line is closed.&lt;/p&gt;

&lt;p&gt;To stop use &lt;code&gt;docker-compose down&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Both commands look for a &lt;code&gt;docker-compose.yml&lt;/code&gt; file in the current directory. If it is somewhere else, specify it with &lt;code&gt;-f path/to/docker-compose.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now that the basics of docker-compose are clear, lets move on to Nginx.&lt;/p&gt;
&lt;h2&gt;
  
  
  Nginx
&lt;/h2&gt;

&lt;p&gt;Nginx is a web server with a wide array of features, including reverse proxying, which is what it is used for in this article.&lt;br&gt;&lt;br&gt;
It is configured with a &lt;code&gt;nginx.conf&lt;/code&gt;. By default it looks for it in &lt;code&gt;/etc/nginx/nginx.conf&lt;/code&gt;, but it is of course possible to specify another file.&lt;/p&gt;

&lt;p&gt;As a reverse proxy, it can transparenty handle two very important aspects of a web application, encryption and caching. But before going into detail about that, lets see how the reverse proxy feature itself is configured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http {
  server {
    server_name your.server.url;

    location /yourService1 {
      proxy_pass http://localhost:80;
      rewrite ^/yourService1(.*)$ $1 break;
    }

    location /yourService2 {
      proxy_pass http://localhost:5000;
      rewrite ^/yourService1(.*)$ $1 break;
    }
  }

  server {
    server_name another.server.url;

    location /yourService1 {
      proxy_pass http://localhost:80;
      rewrite ^/yourService1(.*)$ $1 break;
    }

    location /yourService3 {
      proxy_pass http://localhost:5001;
      rewrite ^/yourService1(.*)$ $1 break;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The Nginx config is organized in &lt;strong&gt;contexts&lt;/strong&gt;, which define the kind of traffic they are handling. The &lt;code&gt;http&lt;/code&gt; context is (obviously) handling http traffic. Other contexts are &lt;code&gt;mail&lt;/code&gt; and &lt;code&gt;stream&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;server&lt;/code&gt; configuration specifies a virtual server, where each can have its own rules. The &lt;code&gt;server_name&lt;/code&gt; directive defined which urls or IP addresses the virtual server responds to. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;location&lt;/code&gt; configuration defines where to route incoming traffic. Depending on the url, the requests can be passed to one service or another. In the config above, the start of the route specifies the service.&lt;br&gt;&lt;br&gt;
&lt;code&gt;proxy_pass&lt;/code&gt; sets the new url, and with &lt;code&gt;rewrite&lt;/code&gt; the url is rewritten so that it fits the service. In this case, the &lt;code&gt;yourService{x}&lt;/code&gt; is removed from the url.&lt;/p&gt;

&lt;p&gt;This was a general overview, later sections will explain how caching and SSL can be configured.&lt;/p&gt;

&lt;p&gt;For more details, check out the &lt;a href="https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now that we know the pieces, lets start putting them together.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setup Nginx as a Reverse-Proxy inside Docker
&lt;/h2&gt;

&lt;p&gt;For a basic setup only 3 things are needed: &lt;/p&gt;

&lt;p&gt;1) Mapping of the host ports to the container ports&lt;br&gt;
2) Mapping a config file to the default Nginx config file at &lt;code&gt;/etc/nginx/nginx.conf&lt;/code&gt;&lt;br&gt;
3) The Nginx config&lt;/p&gt;

&lt;p&gt;In a docker-compose file, the port mapping can be done with the &lt;code&gt;ports&lt;/code&gt; config entry, as we've seen above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    ...
    ports:
      - 80:80
      - 443:443
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The mapping for the Nginx config is done with a volume, which we've also seen before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    ...
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The Nginx config is assumed to be in the same directory as &lt;code&gt;docker-compse.yml&lt;/code&gt; (&lt;code&gt;./nginx.conf&lt;/code&gt;), but it can be anywhere of course.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Configuration
&lt;/h3&gt;

&lt;p&gt;Adding caching to the setup is quite easy, only the Nginx config has to be changed.&lt;br&gt;&lt;br&gt;
In the &lt;code&gt;http&lt;/code&gt; context, add a &lt;code&gt;proxy_cache_path&lt;/code&gt; directive, which defines the local filesystem path for cached content and name and size of the memory zone.&lt;br&gt;&lt;br&gt;
Keep in mind though that the path is &lt;em&gt;inside&lt;/em&gt; the container, not on the host's filesystem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http {
    ...
    proxy_cache_path /data/nginx/cache keys_zone=one:10m;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;server&lt;/code&gt; or &lt;code&gt;location&lt;/code&gt; context for which responses should be cached, add a &lt;code&gt;proxy_cache&lt;/code&gt; directive specifying the memory zone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  ...
  server {
    proxy_cache one;
  ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That's enough to define the cache with the default caching configuration. There are a lot of other directives which specify which responses to cache in much more detail. For more details on those, have a look at the &lt;a href="https://docs.nginx.com/nginx/admin-guide/content-cache/content-caching/#specifying-which-requests-to-cache"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Securing HTTP Traffic with SSL
&lt;/h2&gt;

&lt;p&gt;By now the server setup is finished. docker-compose starts up all containers, and the Nginx container acts as a reverse-proxy for the services. There is just one thing left to set up, as &lt;a href="https://doesmysiteneedhttps.com"&gt;this&lt;/a&gt; site so beautifully explains, encryption.&lt;/p&gt;

&lt;p&gt;To install certbot, the client that fetches certificates from Let’s Encrypt, follow the &lt;a href="https://certbot.eff.org"&gt;install instructions&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating SSL Certificates with certbot
&lt;/h3&gt;

&lt;p&gt;certbot has a variety of ways to get SSL certificates. There are plugins for widespread webservers, like Apache and Nginx, one to use a standalone webserver to verify the domain, and of course a manual way.&lt;/p&gt;

&lt;p&gt;We'll use the &lt;code&gt;standalone&lt;/code&gt; plugin. It starts up a separate webserver for the certificate challenge, which means the port 80 or 443 must be available. For this to work, the Nginx webserver has to be shut down, as it binds to both ports, and the certbot server needs to be able to accept inbound connections on at least one of them.&lt;/p&gt;

&lt;p&gt;To create a certificate, execute&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;certbot --standalone -d your.server.url
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and follow the instructions. You can also create a certificate for multiple urls at once, by adding more &lt;code&gt;-d&lt;/code&gt; parameters, e.g. &lt;code&gt;-d your.server1.url&lt;/code&gt; &lt;code&gt;-d your.server2.url&lt;/code&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Automating Certificate Renewal
&lt;/h3&gt;

&lt;p&gt;The Let's Encrypt CA issues short-lived certificates, they are only valid for 90 days. This makes automating the renewal process important. Thankfully, certbot makes that easy with the command &lt;a href="https://certbot.eff.org/docs/using.html#renewing-certificates"&gt;&lt;code&gt;certbot renew&lt;/code&gt;&lt;/a&gt;. It checks all installed certificates, and renews the ones that will expire in less than 30 days. &lt;/p&gt;

&lt;p&gt;It will use the same plugin for the renewal as was used when initially getting the certificate. In our case that is the &lt;code&gt;standalone&lt;/code&gt; plugin. &lt;/p&gt;

&lt;p&gt;The challenge process is the same, so also for renewals the ports 80 or 443 must be free.&lt;br&gt;&lt;br&gt;
certbot provides pre and post hooks, which we use to stop and start the webserver during the renewal, to free the ports.&lt;br&gt;&lt;br&gt;
The hooks are executed only if a certificate needs to be renewed, so there is no unnecessary downtime of your services.&lt;/p&gt;

&lt;p&gt;Since we are using &lt;code&gt;docker-compose&lt;/code&gt;, the whole command looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;certbot renew --pre-hook "docker-compose -f path/to/docker-compose.yml down" --post-hook "docker-compose -f path/to/docker-compose.yml up -d"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To complete the automation simply add the previous command as a cronjob.&lt;br&gt;&lt;br&gt;
Open the cron file with &lt;code&gt;crontab -e&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
In there add a new line with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@daily certbot renew --pre-hook "docker-compose -f path/to/docker-compose.yml down" --post-hook "docker-compose -f path/to/docker-compose.yml up -d"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That's it. Now the renew command is executed daily, and you won't have to worry about your certificates' expiration date.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the Certificates in the Nginx Docker Container
&lt;/h3&gt;

&lt;p&gt;By now the certificates are requested and stored on the server, but we don't use them yet. To achieve that, we have to&lt;/p&gt;

&lt;p&gt;1) Make the certificates available to the Nginx container and&lt;br&gt;
2) Change the config to use them&lt;/p&gt;

&lt;p&gt;To make the certificates available to the Nginx container, simply specify the whole &lt;code&gt;letsencrypt&lt;/code&gt; directory as a volume on it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  ...
  nginx: 
    image: nginx:latest
    container_name: production_nginx
    volumes:
      - /etc/letsencrypt/:/etc/letsencrypt/
  ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Adapting the config and making it secure is a bit more work.&lt;br&gt;
By default, a virtual server listens to port 80, but with SSL, it should also listen to port 443. This has to be specified by 2 &lt;code&gt;listen&lt;/code&gt; directives.&lt;br&gt;&lt;br&gt;
Additionally, the certificate must be defined. This is done with the &lt;code&gt;ssl_certificate&lt;/code&gt; and &lt;code&gt;ssl_certificate_key&lt;/code&gt; directives.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  ...
  server {
    ...
    listen 80;
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/your.server.url/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your.server.url/privkey.pem;
  }
  ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;These small changes are enough to configure nginx for SSL.&lt;br&gt;&lt;br&gt;
It uses the default SSL settings of Nginx though, which is ok, but can be improved upon.&lt;/p&gt;
&lt;h3&gt;
  
  
  Improving Security of Nginx Config
&lt;/h3&gt;

&lt;p&gt;At the beginning of this section I should mention that, if you use the latest version of nginx, its default SSL settings are secure. There is no need to define the protocols, ciphers and other parameters.&lt;/p&gt;

&lt;p&gt;That said, there are a few SSL directives with which we can improve security even further.&lt;br&gt;&lt;br&gt;
Just keep in mind that by setting these, you are responsible for keeping them up to date yourself. The changes Nginx does to the default config settings won't affect you, since you're overwriting them.&lt;/p&gt;

&lt;p&gt;First, set&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssl_protocols TLSv1.1 TLSv1.2;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This disables all SSL protocols and TLSv1.0, which are considered insecure (&lt;a href="https://www.netsparker.com/web-vulnerability-scanner/vulnerabilities/insecure-transportation-security-protocol-supported-tls-10/"&gt;TLSv1.0&lt;/a&gt;, &lt;a href="https://www.netsparker.com/web-vulnerability-scanner/vulnerabilities/insecure-transportation-security-protocol-supported-sslv3/"&gt;SSLv3&lt;/a&gt;, &lt;a href="https://www.netsparker.com/web-vulnerability-scanner/vulnerabilities/insecure-transportation-security-protocol-supported-sslv2/"&gt;SSLv2&lt;/a&gt;). TLSv1.1 and TLSv1.2 are, at the time of writing (July 2018), considered secure, but nobody can promise that they will not be broken in the future. &lt;/p&gt;

&lt;p&gt;Next, set&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssl_prefer_server_ciphers on;
ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DHE+AES128:!ADH:!AECDH:!MD5;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The ciphers define how the encryption is done. Those values are copied from &lt;a href="https://bjornjohansen.no/optimizing-https-nginx"&gt;this article&lt;/a&gt;, as I'm not an expert in this area.&lt;/p&gt;

&lt;p&gt;Those are the most important settings. To improve security even more, follow these articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://bjornjohansen.no/optimizing-https-nginx"&gt;Optimizing HTTPS on Nginx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/cecilemuller/a26737699a7e70a7093d4dc115915de8#stronger-settings-for-a"&gt;How to setup Let's Encrypt for Nginx on Ubuntu 18.04&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can check the security of your SSL configuration with a great &lt;a href="https://www.ssllabs.com/ssltest/analyze.html"&gt;website&lt;/a&gt; SSL Labs provides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;In this article we've covered how to setup docker-compose, use its network and volume feature and how to set environment variables, how to use Nginx as a reverse proxy, including caching and SSL security. Everything that's needed to host a project.&lt;/p&gt;

&lt;p&gt;Just keep in mind that this is not a terribly professional setup, any important service will need a more sophisticated setup, but for small projects or side-projects it is totally fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Amendment
&lt;/h2&gt;

&lt;p&gt;Here are the resulting &lt;code&gt;nginx.conf&lt;/code&gt; and &lt;code&gt;docker-compose.yml&lt;/code&gt; files. They include placeholder names, urls and paths for your applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;docker-compose.yml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3'
services:
  nginx: 
    image: nginx:latest
    container_name: production_nginx
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/error.log:/etc/nginx/error_log.log
      - ./nginx/cache/:/etc/nginx/cache
      - /etc/letsencrypt/:/etc/letsencrypt/
    ports:
      - 80:80
      - 443:443

  your_app_1:
    image: your_app_1_image:latest
    container_name: your_app_1
    expose:
      - "80"

  your_app_2:
    image: your_app_2_image:latest
    container_name: your_app_2
    expose:
      - "80"

  your_app_3:
    image: your_app_3_image:latest
    container_name: your_app_3
    expose:
      - "80"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;nginx.conf&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;events {

}

http {
  error_log /etc/nginx/error_log.log warn;
  client_max_body_size 20m;

  proxy_cache_path /etc/nginx/cache keys_zone=one:500m max_size=1000m;

  server {
    server_name server1.your.domain;

    location /your_app_1 {
      proxy_pass http://your_app_1:80;
      rewrite ^/your_app_1(.*)$ $1 break;
    }

    location /your_app_2 {
      proxy_pass http://your_app_2:80;
      rewrite ^/your_app_2(.*)$ $1 break;
    }
  }

  server {
    server_name server2.your.domain;
    proxy_cache one;
    proxy_cache_key $request_method$request_uri;
    proxy_cache_min_uses 1;
    proxy_cache_methods GET;
    proxy_cache_valid 200 1y;

    location / {
      proxy_pass http://your_app_3:80;
      rewrite ^/your_app_3(.*)$ $1 break;
    }

    listen 80;
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/server2.your.domain/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/server2.your.domain/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;






&lt;p&gt;Follow me on &lt;a href="https://twitter.com/Domysee"&gt;Twitter&lt;/a&gt; for more of my thoughts, articles, projects and work.&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>dockercompose</category>
      <category>server</category>
      <category>devops</category>
    </item>
    <item>
      <title>Do password rules impact security?</title>
      <dc:creator>Dominik Weber</dc:creator>
      <pubDate>Sun, 08 Jul 2018 12:59:32 +0000</pubDate>
      <link>https://dev.to/domysee/do-password-rules-impact-security-137n</link>
      <guid>https://dev.to/domysee/do-password-rules-impact-security-137n</guid>
      <description>&lt;p&gt;Recently I had a shower thought about passwords, namely that any rules we apply limit the possible passwords the user can choose. Which also means that there are less passwords an attacker has to go through when brute-forcing a password. &lt;/p&gt;

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

&lt;p&gt;For simplicity I chose 4 character groups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;lowercase letters: 26&lt;/li&gt;
&lt;li&gt;uppercase letters: 26&lt;/li&gt;
&lt;li&gt;numerals: 10&lt;/li&gt;
&lt;li&gt;special characters: 34&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The calculations assume that there has to be at least 1 character of each group in the password. &lt;/p&gt;

&lt;p&gt;This is not exhaustive of course, but I have to start somewhere. &lt;/p&gt;

&lt;h2&gt;
  
  
  Impact
&lt;/h2&gt;

&lt;p&gt;In total there are &lt;strong&gt;96&lt;/strong&gt; allowed characters.&lt;br&gt;&lt;br&gt;
This means, for an 6 character long password, without any rules, there are &lt;code&gt;96^6&lt;/code&gt;, or &lt;code&gt;782.757.789.696&lt;/code&gt;, options. Almost &lt;strong&gt;800 billion&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
With the rules above applied, there are only &lt;code&gt;26*26*10*34*96^2&lt;/code&gt;, or &lt;code&gt;2.118.205.440&lt;/code&gt;, options. Just a bit more than &lt;strong&gt;2 billion&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's a &lt;strong&gt;370x difference&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;To put that in perspective, instead of 1 year, it will take an attacker less than 1 day to brute-force that password.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is that bad?
&lt;/h2&gt;

&lt;p&gt;To be honest, I don't know. It sounds like much, but on the other hand, increasing the length by ~1.3 characters will negate the difference. So a password that's 1 character longer will almost do so, and one that's 2 characters longer will provide even better security.&lt;/p&gt;

&lt;p&gt;What are your opinions on that topic? Please comment below 👇&lt;/p&gt;




&lt;p&gt;Follow me on &lt;a href="https://twitter.com/Domysee"&gt;Twitter&lt;/a&gt; for more of my thoughts, articles, projects and work.&lt;/p&gt;

</description>
      <category>security</category>
      <category>passwords</category>
      <category>rules</category>
    </item>
    <item>
      <title>Coloring white Images with CSS filter</title>
      <dc:creator>Dominik Weber</dc:creator>
      <pubDate>Sun, 17 Jun 2018 00:00:00 +0000</pubDate>
      <link>https://dev.to/domysee/coloring-white-images-with-css-filter-4bd1</link>
      <guid>https://dev.to/domysee/coloring-white-images-with-css-filter-4bd1</guid>
      <description>&lt;p&gt;Recently I had to change the color of a white image. Since this image was rendered by a library, which did not allow replacing it by another one, it had to be done with CSS. Although there is a relatively simple solution to it, it proved to be quite a challenge to come up with that.&lt;/p&gt;

&lt;p&gt;Lets get to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should be easy!
&lt;/h2&gt;

&lt;p&gt;Just add a &lt;code&gt;hue-rotate&lt;/code&gt; filter, that should work.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/domysee/embed/XYzaor/?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;But it doesn’t. Of course not, that would be too easy. The image is still white.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why didn’t it work?
&lt;/h2&gt;

&lt;p&gt;After checking a few times if the filter really was applied, it dawned upon me.&lt;br&gt;&lt;br&gt;
White has a luminocity value of 100%, so changing the hue won’t do anything. In fact, white has a hue value of 0, which is red. So I should have known beforehand that changing the hue won’t work, silly me!&lt;/p&gt;
&lt;h2&gt;
  
  
  Change the luminosity then
&lt;/h2&gt;

&lt;p&gt;Since 100% luminocity is white, and 0% is black, 50% should result in a rich color. Lets try that.&lt;/p&gt;

&lt;p&gt;Changing the luminocity can be achieved by the &lt;code&gt;brightness&lt;/code&gt; filter, which changes the brightness based on the given factor.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/domysee/embed/xzPLNZ/?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Okay, something changed, that’s good. But the result is not as expected. There is no color. The image is just gray.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened?
&lt;/h2&gt;

&lt;p&gt;The luminocity is now 50%, as expected. But I forgot about the saturation. Since white has 100% luminocity, I thought that saturation would also be 100%. Unfortunately, it is 0%, which causes the gray color.&lt;/p&gt;

&lt;h2&gt;
  
  
  So change the saturation
&lt;/h2&gt;

&lt;p&gt;If there is a &lt;code&gt;brightness&lt;/code&gt; filter, there surely is a &lt;code&gt;saturate&lt;/code&gt; filter. And yes, there is!&lt;br&gt;&lt;br&gt;
Lets set the saturation to 100% then:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/domysee/embed/LrOzVx/?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Again, nothing changed.&lt;br&gt;&lt;br&gt;
Turns out, the &lt;code&gt;saturate&lt;/code&gt; filter works the same way as the &lt;code&gt;brightness&lt;/code&gt; filter. It just changes the value based on the given &lt;em&gt;factor&lt;/em&gt;. Now that’s a problem. As primary school math taught us, &lt;em&gt;everything&lt;/em&gt; times &lt;code&gt;0&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
This makes the &lt;code&gt;saturate&lt;/code&gt; filter useless for this purpose.&lt;/p&gt;
&lt;h2&gt;
  
  
  Time to give up?
&lt;/h2&gt;

&lt;p&gt;Not yet! There are a few other filters to try. Lets go through them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;blur&lt;/code&gt;: obviously doesn’t change the color&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contrast&lt;/code&gt;: does nothing in a gray image&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;drop-shadow&lt;/code&gt;: also doesn’t change the color&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;grayscale&lt;/code&gt;: well, can’t make a gray image gray_er_&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hue-rotate&lt;/code&gt;: the whole point is to make the hue visible, a &lt;em&gt;different&lt;/em&gt; hue won’t change anything&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;invert&lt;/code&gt;: inverting gray, sadly, just results in a different variation of gray&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;opacity&lt;/code&gt;: it would be possible to make another color shine through, but that’s not what I’m after&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sepia&lt;/code&gt;: that’s the last one. And finally, it looks like it could work!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/filter" rel="noopener noreferrer"&gt;MDN documentation&lt;/a&gt; example looks very promising.&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/http%3A%2F%2Fdomysee.com%2Fblogposts%2F21%2Fsepia-filter.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/http%3A%2F%2Fdomysee.com%2Fblogposts%2F21%2Fsepia-filter.png" alt="Sepia Filter Example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s try it out:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/domysee/embed/rKYGLO/?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Did that change something? It looks like white. &lt;em&gt;Almost&lt;/em&gt;. By checking with the color picker I found out that the actual color is &lt;code&gt;hsl(60, 100%, 97%)&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
That means, we’ve got saturation! Finally some results 🎉&lt;/p&gt;

&lt;p&gt;From there it is now possible to apply the other filters to achieve the desired result.&lt;/p&gt;
&lt;h2&gt;
  
  
  Lets see some color!
&lt;/h2&gt;

&lt;p&gt;I have written enough about trial and error already. Let me save you some time and show you how to get a pure red:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/domysee/embed/rKYzJg/?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;In my opinion this is the easiest color to work from to get whatever color you want.&lt;br&gt;&lt;br&gt;
For example, to get green, just add a &lt;code&gt;hue-rotate(120deg)&lt;/code&gt; at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;White is a nasty color to change with filters. With the typical filters, it is impossible to create a color other than grayscale.&lt;br&gt;&lt;br&gt;
The &lt;code&gt;sepia&lt;/code&gt; filter might not have been designed for this purpose, but it saves the day!&lt;/p&gt;

&lt;h2&gt;
  
  
  One more thing: black
&lt;/h2&gt;

&lt;p&gt;To turn black into a color, turn it to white and work from there:&lt;/p&gt;

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

img {
  filter: invert(1) brightness(50%) sepia(100%) saturate(10000%);
}


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

&lt;/div&gt;




&lt;p&gt;Follow me on &lt;a href="https://twitter.com/Domysee" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; for more of my thoughts, articles, projects and work.&lt;/p&gt;

</description>
      <category>css</category>
      <category>color</category>
      <category>web</category>
    </item>
    <item>
      <title>Hosting Asp.Net Core Applications on Windows Server Core</title>
      <dc:creator>Dominik Weber</dc:creator>
      <pubDate>Wed, 04 Apr 2018 22:13:11 +0000</pubDate>
      <link>https://dev.to/domysee/hosting-aspnet-core-applications-on-windows-server-core-289g</link>
      <guid>https://dev.to/domysee/hosting-aspnet-core-applications-on-windows-server-core-289g</guid>
      <description>&lt;p&gt;Recently, I've found myself in the position of having to host an application on Windows Server. Having never managed a Windows Server before, I struggled to find relevant information, especially since most of it is written for a Windows Server with installed UI, and the default image on Azure is a Core image, without UI. This is mostly documentation for myself, but maybe you find it helpful too.&lt;/p&gt;

&lt;p&gt;This is a cross-post from my &lt;a href="http://www.domysee.com/blogposts/blogpost%2019%20-%20hosting%20asp/" rel="noopener noreferrer"&gt;personal blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This is a step by step introduction of how to host an Asp.Net Core application on Windows Server Core with IIS (Internet Information Server).&lt;/p&gt;

&lt;p&gt;We will cover how to set up IIS, how to configure it, how to deploy to it with Web Deploy in Visual Studio and securing connections to that application with https.&lt;/p&gt;

&lt;p&gt;I'm using a virtual machine from Azure, which provides a nice UI for managing firewall rules. That is probably very different for you, so I'll just say which ports have to be open, and not cover how to do that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the Server
&lt;/h2&gt;

&lt;p&gt;After logging in on the server, you are greeted by a command prompt. Since most commands we will use are PowerShell commands, we have to start it.&lt;br&gt;&lt;br&gt;
Just enter &lt;code&gt;powershell&lt;/code&gt; and execute it. After that you should see a &lt;code&gt;PS&lt;/code&gt; in front of the prompt.&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/http%3A%2F%2Fwww.domysee.com%2Fblogposts%2F19%2F1-powershell.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/http%3A%2F%2Fwww.domysee.com%2Fblogposts%2F19%2F1-powershell.png" alt="PowerShell Prompt"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now IIS has to be installed. This is done with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Install-WindowsFeature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Web-Server&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While installing, PowerShell shows a nice little progress bar:&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/http%3A%2F%2Fwww.domysee.com%2Fblogposts%2F19%2F2-install-progress.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/http%3A%2F%2Fwww.domysee.com%2Fblogposts%2F19%2F2-install-progress.png" alt="PowerShell Progress"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling Remote Management
&lt;/h2&gt;

&lt;p&gt;Per default, the server does not allow remote management. It has to be enabled by installing the Web-Mgmt-Service and setting the registry entry &lt;code&gt;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WebManagement\Server\EnableRemoteManagement&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Keep in mind that the registry key is only available after Web-Mgmt-Service is installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Install-WindowsFeature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Web-Mgmt-Service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Set-ItemProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WebManagement\Server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;EnableRemoteManagement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After executing those commands restart the web server so that the changes take effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;was&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/y&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;w3svc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also start the Web Management Service, otherwise you won't be able to connect to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wmsvc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; IIS Manager connects via port 8172, so make sure it is open on your server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling Management on your Windows 10 Device
&lt;/h2&gt;

&lt;p&gt;To remotly manage an IIS server, the IIS Manager has to be installed on your device. This can be done in &lt;code&gt;Control Panel -&amp;gt; Programs -&amp;gt; Programs and Features -&amp;gt; Turn Windows features on or off&lt;/code&gt;. Activating &lt;code&gt;IIS Management Console&lt;/code&gt; is sufficient, IIS itself does not have to be installed.&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/http%3A%2F%2Fwww.domysee.com%2Fblogposts%2F19%2F3-iis-activation.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/http%3A%2F%2Fwww.domysee.com%2Fblogposts%2F19%2F3-iis-activation.png" alt="IIS Manager Installation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Out of the box IIS Manager cannot manage remote servers. That features has to be added with &lt;em&gt;IIS Manager for Remote Administration&lt;/em&gt;. You can download it &lt;a href="https://www.microsoft.com/en-us/download/details.aspx?id=41177" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
After it is installed, IIS Manager will have the menus enabled to connect to a remote IIS.&lt;/p&gt;

&lt;p&gt;Now the connection to the remote IIS can be added. Just go to &lt;code&gt;File -&amp;gt; Connect to a Server&lt;/code&gt; and fill out the required information.&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/http%3A%2F%2Fwww.domysee.com%2Fblogposts%2F19%2F4-connect-iis.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/http%3A%2F%2Fwww.domysee.com%2Fblogposts%2F19%2F4-connect-iis.png" alt="IIS Manager Installation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you can't connect, most likely the Port 8172 is not open, or the Web Management Service is not started. Do that with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wmsvc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuring IIS to host Asp.Net Core Applications
&lt;/h2&gt;

&lt;p&gt;By default IIS cannot host Asp.Net Core applications. The &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/aspnet-core-module" rel="noopener noreferrer"&gt;Asp.Net Core Module&lt;/a&gt; is needed for that, which is installed with the .NET Core Windows Server Hosting bundle.  &lt;/p&gt;

&lt;p&gt;1) Go to the &lt;a href="https://www.microsoft.com/net/download/all" rel="noopener noreferrer"&gt;.Net all downloads page&lt;/a&gt;&lt;br&gt;
2) Select the .Net Core runtime you need&lt;br&gt;
3) Download Server Hosting Installer (this is just to copy the download url, we need it on the server, not locally)&lt;br&gt;
4) Copy the download url&lt;br&gt;
5) Download the installer on the server with the command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://download.microsoft.com/download/8/D/A/8DA04DA7-565B-4372-BBCE-D44C7809A467/DotNetCore.2.0.6-1-WindowsHosting.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-OutFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Users\YourUsername\Downloads\DotNetCore.2.0.6-1-WindowsHosting.exe&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#This is the download url for the latest non-preview runtime at the time of writing (2.0.6).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;6) Execute the installer&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;C:\Users\YourUsername\Downloads\DotNetCore.2.0.6-1-WindowsHosting.exe&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, this is what was really surprising for me. The installer executes with a UI, the same as on any Windows. Being on a Core installation, I thought there would be absolutely no UI, but I was wrong.&lt;br&gt;&lt;br&gt;
This also opens the interesting option to install Chrome and download all necessary files with it. &lt;/p&gt;

&lt;p&gt;Restart the web server so that the changes take effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;was&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/y&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;w3svc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Preparing IIS for Web Deploy
&lt;/h2&gt;

&lt;p&gt;Since this is a small project, the most convenient deploy option is Web Deploy directly in Visual Studio.&lt;br&gt;&lt;br&gt;
As with almost everything else, this is not supported out of the box, but can be added.  &lt;/p&gt;

&lt;p&gt;Web Deploy can be downloaded from the &lt;a href="https://www.microsoft.com/en-us/download/details.aspx?id=43717" rel="noopener noreferrer"&gt;Microsoft Download Center&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
Use the same process outlined above, or Chrome, your choice :-)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://download.microsoft.com/download/0/1/D/01DC28EA-638C-4A22-A57B-4CEF97755C6C/WebDeploy_amd64_en-US.msi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-OutFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Users\dominik\Downloads\WebDeploy_amd64_en-US.msi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#This is the download url for the latest Web Deploy at the time of writing (3.6).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also execute that installer&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;C:\Users\dominik\Downloads\WebDeploy_amd64_en-US.msi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; I've read somewhere that all features have to be installed, and that the installer's &lt;em&gt;Complete&lt;/em&gt; option does not actually install everything. So just select &lt;em&gt;Custom&lt;/em&gt; and make sure to that all features are enabled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying an Asp.Net Core Application
&lt;/h2&gt;

&lt;p&gt;Now we are finally ready to publish the application. Well, almost. A publish profile has to be created first.&lt;/p&gt;

&lt;p&gt;1) Right-click on the Asp.Net Core application in the Solution Explorer&lt;br&gt;
2) Select &lt;em&gt;Publish&lt;/em&gt;&lt;br&gt;
3) Click on &lt;em&gt;Create new Profile&lt;/em&gt;&lt;br&gt;
4) Select &lt;em&gt;IIS, FPT, etc.&lt;/em&gt;&lt;br&gt;
5) Select &lt;em&gt;Create Profile&lt;/em&gt; where by default &lt;em&gt;Publish&lt;/em&gt; is entered&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/http%3A%2F%2Fwww.domysee.com%2Fblogposts%2F19%2F5-publish-target.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/http%3A%2F%2Fwww.domysee.com%2Fblogposts%2F19%2F5-publish-target.png" alt="IIS Manager Installation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;6) Enter the required information&lt;br&gt;
    - &lt;em&gt;Site name&lt;/em&gt; is either &lt;em&gt;Default Web Site&lt;/em&gt;, or, if you created a different one in IIS, the name of that one.&lt;br&gt;
7) Click &lt;em&gt;Validate Connection&lt;/em&gt; to check if everything was entered correctly&lt;br&gt;
8) If it was, click &lt;em&gt;Save&lt;/em&gt;&lt;br&gt;
9) Select the created profile&lt;br&gt;
10) Click &lt;em&gt;Publish&lt;/em&gt; and watch the magic happen :-)&lt;/p&gt;
&lt;h2&gt;
  
  
  Configuring SSL
&lt;/h2&gt;

&lt;p&gt;We've achieved what we wanted, hosting the application. Now there is only one step left: securing it with SSL. Don't worry, it's not difficult, I promise.&lt;br&gt;&lt;br&gt;
There is a great project out there, called &lt;a href="https://github.com/PKISharp/win-acme" rel="noopener noreferrer"&gt;Windows ACME Simple&lt;/a&gt;, which makes this process really simple.&lt;/p&gt;

&lt;p&gt;1) Download the latest release (you can get the download link from the release page of the Github project)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://github.com/PKISharp/win-acme/releases/download/v1.9.10.1/win-acme.v1.9.10.1.zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-OutFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Users\dominik\Downloads\win-acme.v1.9.10.1.zip&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#This is the download url for the latest version at the time of writing (1.9.10.1).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) If this fails with the message &lt;code&gt;The request was aborted: Could not create SSL/TLS secure channel.&lt;/code&gt;, try execute &lt;code&gt;[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12&lt;/code&gt; beforehand (from &lt;a href="https://stackoverflow.com/a/41618979/3107430" rel="noopener noreferrer"&gt;StackOverflow&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;3) Extract the zip file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Expand-Archive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Users\dominik\Downloads\win-acme.v1.9.10.1.zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-DestinationPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Users\dominik\Downloads\win-acme.v1.9.10.1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4) Execute &lt;em&gt;letsencrypt.exe&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;C:\Users\dominik\Downloads\win-acme.v1.9.10.1\letsencrypt.exe&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fwww.domysee.com%2Fblogposts%2F19%2F6-letsencrypt.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/http%3A%2F%2Fwww.domysee.com%2Fblogposts%2F19%2F6-letsencrypt.png" alt="IIS Manager Installation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;5) Select &lt;code&gt;N&lt;/code&gt; to create a new certificate in simple mode&lt;br&gt;
6) Select &lt;code&gt;1&lt;/code&gt; to create a single binding of an IIS site&lt;br&gt;
7) Now you should see a selection of sites you have configured. Select the site you want to secure&lt;br&gt;
8) After you've added an email address and agreed to the subscriber agreement, it does its magic&lt;br&gt;
9) If all goes well, your site is now encrypted and you can quit Windows ACME Simple (&lt;code&gt;Q&lt;/code&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;That's it. The application is now fully set up. I hope this walkthrough helped you as much as it undoubtedly will help me in the future, the next time I have to set up a Windows Server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/windows-server/get-started/get-started-with-1709" rel="noopener noreferrer"&gt;Introducing Windows Server, version 1709&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/windows-server/administration/server-core/server-core-manage" rel="noopener noreferrer"&gt;Manage a Server Core server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blogs.msdn.microsoft.com/benjaminperkins/2015/11/02/configure-an-iis-server-core-server-for-remote-management/" rel="noopener noreferrer"&gt;Configure an IIS Server Core server for remote management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/?tabs=aspnetcore2x" rel="noopener noreferrer"&gt;Host ASP.NET Core on Windows with IIS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Follow me on &lt;a href="https://twitter.com/Domysee" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; for more of my thoughts, articles, projects and work.&lt;/p&gt;

</description>
      <category>windowsservercore</category>
      <category>devops</category>
      <category>aspnetcore</category>
      <category>iis</category>
    </item>
    <item>
      <title>Using Scripting to quickly execute repetitive Tasks</title>
      <dc:creator>Dominik Weber</dc:creator>
      <pubDate>Sun, 18 Feb 2018 11:59:39 +0000</pubDate>
      <link>https://dev.to/domysee/using-scripting-to-quickly-execute-repetitive-tasks--cp2</link>
      <guid>https://dev.to/domysee/using-scripting-to-quickly-execute-repetitive-tasks--cp2</guid>
      <description>&lt;p&gt;This is a cross-post from my &lt;a href="http://www.domysee.com/blogposts/blogpost%2018%20-%20using%20scripting%20to%20quickly%20execute%20repetitive%20tasks/" rel="noopener noreferrer"&gt;personal blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Sometimes I have to do a repetitive task for a project, that would take hours if done manually. Shell commands are normally the way to go, but I've always had a hard time learning them, and after I did, I forgot them quickly, because I need them very infrequently. So I found another solution.&lt;/p&gt;

&lt;p&gt;Scripting! Especially with &lt;strong&gt;interactive ruby&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, recently I had to rename a bunch of files, removing &lt;em&gt;".inline"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The files I had looked like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;icon.inline.svg&lt;br&gt;
logo.inline.svg&lt;br&gt;
(and a bunch more)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And they should be renamed to&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;icon.svg&lt;br&gt;
logo.svg&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Okay, lets do this.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to the directory where the files are and start the interactive ruby shell with &lt;code&gt;irb&lt;/code&gt; (if Ruby is installed)
&lt;/li&gt;
&lt;li&gt;Get all filenames in a variable:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;001&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"*.svg"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Rename them in a loop:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;002&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;003&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;".inline"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;004&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done. Quite easy, right?&lt;/p&gt;

&lt;p&gt;Now, this is a simple use-case. Looking up a shell command wouldn't take too long.&lt;br&gt;&lt;br&gt;
But what if it gets more complicated?&lt;br&gt;&lt;br&gt;
What if the file needs to contain a special text? What if the file size has to exceed a particular limit? What if ...&lt;/p&gt;

&lt;p&gt;You know where I'm getting at.&lt;br&gt;&lt;br&gt;
With Ruby, I know what to do. Just look up whatever I don't know and combine everything with the constructs I, as a developer, am familiar with. &lt;/p&gt;

&lt;p&gt;Since I have never extensively used it, I'd have a lot more problems with shell commands/scripts. I'd need to look up how to do the subproblems (e.g. getting the file size) AND how to combine them with pipes, arrows and whatever else exists.&lt;/p&gt;

&lt;p&gt;Additionally, Ruby statements are usually a lot easier to remember. Staying with the example of getting the file size, this is how it works with bash (&lt;a href="https://unix.stackexchange.com/a/16644/276610" rel="noopener noreferrer"&gt;accepted answer in the first Google result&lt;/a&gt;):&lt;br&gt;
&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;stat&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt;%z icon.inline.svg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Ruby:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"icon.inline.svg"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doesn't Ruby look a lot more straightforward?&lt;/p&gt;

&lt;p&gt;So, if you're like me and don't know how to do complex tasks with shell, give &lt;a href="https://en.wikipedia.org/wiki/Interactive_Ruby_Shell" rel="noopener noreferrer"&gt;interactive ruby&lt;/a&gt; a chance. It will infinitely speed up those annoying repetitive tasks.&lt;/p&gt;




&lt;p&gt;Follow me on &lt;a href="https://twitter.com/Domysee" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; for more of my thoughts, articles, projects and work.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Keeping the footer at the bottom with CSS Flexbox</title>
      <dc:creator>Dominik Weber</dc:creator>
      <pubDate>Sun, 04 Feb 2018 22:13:37 +0000</pubDate>
      <link>https://dev.to/domysee/keeping-the-footer-at-the-bottom-with-css-flexbox-5h5f</link>
      <guid>https://dev.to/domysee/keeping-the-footer-at-the-bottom-with-css-flexbox-5h5f</guid>
      <description>&lt;p&gt;Just now I've read the post &lt;a href="https://dev.to/niorad/keeping-the-footer-at-the-bottom-with-css-grid-15mf"&gt;Keeping the Footer at the Bottom with CSS-Grid&lt;/a&gt;. &lt;br&gt;
While reading, I somehow got the impression that doing the same with 'just' flexbox is difficult. It's not.&lt;/p&gt;

&lt;p&gt;This is not to belittle the referenced post in any way, it is very well explained, and in my opinion more elegant than with flexbox. I just want to explain that it is as easy with flexbox as it is with grid, so that if, for whatever reason, you cannot use grid, you don't shy away from implementing it with flexbox.&lt;/p&gt;

&lt;p&gt;I'll use the same HTML as in the referenced article:&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;html&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;article&amp;gt;
            &amp;lt;header&amp;gt;
            &amp;lt;/header&amp;gt;
            &amp;lt;main&amp;gt;
            &amp;lt;/main&amp;gt;
            &amp;lt;footer&amp;gt;
            &amp;lt;/footer&amp;gt;
        &amp;lt;/article&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here the CSS to put the footer at the bottom, where it belongs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;html, body {
    width: 100%;
    height: 100%;
}

article {
    min-height: 100%;
    display: flex;
    flex-direction: column;
    align-items: stretch;
}

main {
    flex-grow: 1;
}

header, main, footer {
    flex-shrink: 0;
}

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

&lt;/div&gt;



&lt;p&gt;On the container, we set flexbox to align the contents in a column. The elements should stretch so that they span the whole width, and not just the width their contents take up.&lt;/p&gt;

&lt;p&gt;Setting &lt;code&gt;flex-grow: 1&lt;/code&gt; on main makes it grow to fill the available space. This puts the footer at the bottom, since &lt;code&gt;main&lt;/code&gt; takes up all the space in the middle.&lt;/p&gt;

&lt;p&gt;The use for &lt;code&gt;flex-shrink: 0&lt;/code&gt; is probably less obvious, and it is often forgotten. At least I forgot it way more often than I'd like to admit.  &lt;/p&gt;

&lt;p&gt;By default, &lt;code&gt;flex-shrink&lt;/code&gt; is set to &lt;code&gt;1&lt;/code&gt;. This makes the items shrink if there is not enough space, which happens if the content is larger than the screen. The results can look very weird, e.g. a button that is smaller than the text it contains. Setting it to &lt;code&gt;0&lt;/code&gt; stops that behavior. &lt;/p&gt;

&lt;p&gt;That's it. That's everything you need to know to position the footer at the bottom with flexbox. :)&lt;/p&gt;




&lt;p&gt;Follow me on &lt;a href="https://twitter.com/Domysee"&gt;Twitter&lt;/a&gt; for more of my thoughts, articles, projects and work.&lt;/p&gt;

</description>
      <category>css</category>
      <category>footer</category>
      <category>flexbox</category>
    </item>
    <item>
      <title>What are good Resources to learn about core IT Topics?</title>
      <dc:creator>Dominik Weber</dc:creator>
      <pubDate>Sun, 17 Dec 2017 14:04:19 +0000</pubDate>
      <link>https://dev.to/domysee/what-are-good-resources-to-learn-about-core-it-topics-dpf</link>
      <guid>https://dev.to/domysee/what-are-good-resources-to-learn-about-core-it-topics-dpf</guid>
      <description>&lt;p&gt;Recently, I've talked to some engineer friends, and some core IT topics came up. Topics like virtualization, specific protocols like &lt;a href="https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security"&gt;HSTS&lt;/a&gt; or how browsers work.&lt;/p&gt;

&lt;p&gt;During this conversation, I realized that I don't know enough about these topics, and now I want to learn. &lt;/p&gt;

&lt;p&gt;The thing is, getting an overview of these areas is enough for me. 10 hours of knowledge on a specific topic is too much, and I won't remember the details anyway. &lt;/p&gt;

&lt;p&gt;Finding such resources proves difficult, because I cannot even properly put them together in a category. &lt;/p&gt;

&lt;p&gt;So, there are 2 questions. Do you know what I mean? And if so, how would you categorize it? And do you know about any resources for that?&lt;/p&gt;

&lt;p&gt;Podcasts would be ideal, so I can listen to it on my commute, but anything is welcome.&lt;/p&gt;




&lt;p&gt;Follow me on &lt;a href="https://twitter.com/Domysee"&gt;Twitter&lt;/a&gt; for more of my thoughts, articles, projects and work.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>learning</category>
    </item>
    <item>
      <title>The Bliss of Automating my Server Setup</title>
      <dc:creator>Dominik Weber</dc:creator>
      <pubDate>Thu, 14 Dec 2017 20:15:56 +0000</pubDate>
      <link>https://dev.to/domysee/the-bliss-of-automating-my-server-setup-9d9</link>
      <guid>https://dev.to/domysee/the-bliss-of-automating-my-server-setup-9d9</guid>
      <description>&lt;p&gt;I have a VPS, on which a few of my side-projects are hosted. Recently, I had to set it up a few times, because I made it unusable (please don't ask me how, it was really stupid). &lt;/p&gt;

&lt;p&gt;Every time this happened, it took me hours to set it up again. Granted, I don't know much about maintaining a server, and you can probably do it a lot faster than me, but you can never be faster than a script. So, after the second time, I finally decided it is time to automate that process. &lt;/p&gt;

&lt;h2&gt;
  
  
  Goals
&lt;/h2&gt;

&lt;p&gt;I'm hosting ElasticSearch and a few .Net Core applications. They are all accessible via an nginx reverse proxy.&lt;br&gt;&lt;br&gt;
Additionally, there are a few cronjobs. &lt;/p&gt;

&lt;p&gt;While writing the script, I decided to add Docker support to all my .Net Core applications, so there is one less thing needing to be installed.&lt;br&gt;&lt;br&gt;
This is extremely easy btw, just right-click -&amp;gt; Add Docker support -&amp;gt; Done. But I digress. &lt;/p&gt;

&lt;p&gt;The script should &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;install Docker, docker-compose and nginx&lt;/li&gt;
&lt;li&gt;pull all Docker images&lt;/li&gt;
&lt;li&gt;copy the nginx and ElasticSearch configuration files to the right locations&lt;/li&gt;
&lt;li&gt;add cronjobs&lt;/li&gt;
&lt;li&gt;start all applications&lt;/li&gt;
&lt;li&gt;configure the server so that after rebooting the applications start automatically&lt;/li&gt;
&lt;li&gt;install other applications that I like (e.g. htop)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Script
&lt;/h2&gt;

&lt;p&gt;Writing the script was simple, quite a lot simpler than I thought it would be. It was basically copy&amp;amp;pasting the commands from the vendor websites and using the Docker CLI, which is extremely well documented. &lt;/p&gt;

&lt;p&gt;Docker: &lt;a href="https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#set-up-the-repository" rel="noopener noreferrer"&gt;https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#set-up-the-repository&lt;/a&gt;&lt;br&gt;&lt;br&gt;
docker-compose: &lt;a href="https://docs.docker.com/compose/install/" rel="noopener noreferrer"&gt;https://docs.docker.com/compose/install/&lt;/a&gt;&lt;br&gt;&lt;br&gt;
nginx: &lt;a href="https://www.nginx.com/resources/admin-guide/installing-nginx-open-source/#prebuilt" rel="noopener noreferrer"&gt;https://www.nginx.com/resources/admin-guide/installing-nginx-open-source/#prebuilt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I did have some minor difficulties with specific software though, which are described in the following 2 sections.&lt;/p&gt;
&lt;h3&gt;
  
  
  ElasticSearch
&lt;/h3&gt;

&lt;p&gt;ElasticSearch has a small fallacy. By default, the &lt;code&gt;vm.max_map_count&lt;/code&gt; environment variable is set to &lt;code&gt;65536&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
This is too low for ES, which results in out of memory exceptions. This is very confusing, if there are a few GB of free memory.&lt;br&gt;&lt;br&gt;
The solution is setting &lt;code&gt;vm.max_map_count&lt;/code&gt; to at least &lt;code&gt;262144&lt;/code&gt;, as mentioned in the &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt;, which is quickly found after some googling.  &lt;/p&gt;
&lt;h3&gt;
  
  
  Cronjobs
&lt;/h3&gt;

&lt;p&gt;Sadly, &lt;code&gt;crontab&lt;/code&gt; does not offer a way to simply add an entry. But as expected, others had the same problem, and Stackoverflow has an easy to use &lt;a href="https://stackoverflow.com/a/9625233/3107430" rel="noopener noreferrer"&gt;solution&lt;/a&gt; for it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(crontab -l 2&amp;gt;/dev/null; echo "@reboot nginx -c /home/ubuntu/nginx/nginx.conf") | crontab -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This also demonstrates how applications start after a server reboot. Simply with an &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-use-cron-to-automate-tasks-on-a-vps" rel="noopener noreferrer"&gt;&lt;code&gt;@reboot&lt;/code&gt;&lt;/a&gt; crontab entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Automating the setup process has brought down the time until my server is ready from hours to minutes. And in those blissful minutes, I don't have to do anything, just watch the script work.&lt;/p&gt;

&lt;p&gt;Additionally, I feel much more comfortable experimenting. If something breaks, I just have to start the script. &lt;/p&gt;

&lt;p&gt;Automation is great!&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%2Fwww.device42.com%2Fblog%2Fwp-content%2Fuploads%2F2016%2F04%2Faatt.jpeg" 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%2Fwww.device42.com%2Fblog%2Fwp-content%2Fuploads%2F2016%2F04%2Faatt.jpeg" alt="Automate all the Things"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PS: If you are interested in the whole script, you can find it &lt;a href="https://domysee.com/pages/server_setup_script/" rel="noopener noreferrer"&gt;here&lt;/a&gt;, minus my credentials of course ;) &lt;/p&gt;




&lt;p&gt;Follow me on &lt;a href="https://twitter.com/Domysee" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; for more of my thoughts, articles, projects and work.&lt;/p&gt;

</description>
      <category>server</category>
      <category>linux</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
