<?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: Pete King</title>
    <description>The latest articles on DEV Community by Pete King (@peteking).</description>
    <link>https://dev.to/peteking</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%2F558590%2Fc7ad6c17-a2b6-4920-ba45-7d2bb950170b.jpg</url>
      <title>DEV Community: Pete King</title>
      <link>https://dev.to/peteking</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/peteking"/>
    <language>en</language>
    <item>
      <title>No Need For Docker Anymore</title>
      <dc:creator>Pete King</dc:creator>
      <pubDate>Fri, 12 Jul 2024 15:42:33 +0000</pubDate>
      <link>https://dev.to/peteking/no-need-for-docker-anymore-3nbi</link>
      <guid>https://dev.to/peteking/no-need-for-docker-anymore-3nbi</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;No need for Docker anymore I hear many people say, well I'd like to express that Docker is incredibly relevant today and into the future - We are not saying goodbye!&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker
&lt;/h2&gt;

&lt;p&gt;Docker was and still is a game-changer in software engineering, offering a containerisation approach to application development, deployment and management.&lt;/p&gt;

&lt;p&gt;Here's a run-down of its significance and the advantages Docker can bring to you and your team:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consistent Environments:&lt;/strong&gt; Docker ensure consistent environments across development, testing, and production stages. By encapsulating applications with their dependencies in isolated containers, Docker eliminates environment-related issues that can plague the development lifecycle.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Streamlined Collaboration:&lt;/strong&gt; Dev Containers, built on Docker's foundation, provide a standardised development environment for your team. Everyone on the product/project gets an identical container with the necessary tools and libraries, fostering a seamless collaboration and eliminating setup discrepancies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Improved Portability:&lt;/strong&gt; Docker applications are inherently portable. Containers can run seamlessly on any Linux machine with Docker installed, regardless of the underlying infrastructure. This simplifies development across various environments from local development machines to cloud platforms.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Faster Development Cycles:&lt;/strong&gt; With Docker, developers can spin-up new environments in seconds. This agility translates to faster development cycles, as developers can quickly test and iterate on their code without lengthly setup times.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Efficient Resource Utilisation:&lt;/strong&gt; Containers share the host operating system's kernel, making them lightweight and resource-efficient compared to virtual machines. This allows you to run more applications on the same underlying hardware, optimising resource utilisation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simplified Scalability:&lt;/strong&gt; Scaling Dockerised applications is a breeze You can easily add or remove containers based on demand, enabling horizontal scaling for increased processing power or handling spikes in traffic. This leads onto container orchestration platforms such as Kubernetes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enhanced Reliability:&lt;/strong&gt; Docker containers isolate applications from each other and the host system, promoting stability and reliability. If one container malfunctions, it won't impact other containers or the underlying system.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What's not to like hey? 😁&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning Curve
&lt;/h2&gt;

&lt;p&gt;I acknowledge that there is a learning curve, but trust me (hopefully you can), the initial investment will pay-off in the long term.&lt;/p&gt;

&lt;p&gt;We can mitigate by adopting a strategic approach that focuses on practical experience alongside foundational knowledge.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start with the Basics:&lt;/strong&gt; Begin by understanding the core concepts like containers, images, and Docker files. Numerous beginner-friendly tutorials and workshops are available online.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hands on Learning:&lt;/strong&gt; The best way to solidify new knowledge and understanding is through practical application. Work on small projects, maybe personal ones using Docker. This will help you grasp how Docker functions in real-world applications.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Leverage Online Resources:&lt;/strong&gt; The Docker community is extensive and supportive. Utilise online forums, communities and of course the official Docker documentation to find answers, troubleshoot issues, and learn from other peoples experiences.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Focus on Practical Use Cases:&lt;/strong&gt; Instead of getting bogged down by every minute detail, concentrate on how Docker can address your specific development needs. This will make the learning process more engaging and goal-orientated.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Docker, along with Dev Containers, streamlines the software development process by ensuring &lt;strong&gt;consistency&lt;/strong&gt;, &lt;strong&gt;portability&lt;/strong&gt;, &lt;strong&gt;efficiency&lt;/strong&gt;, and &lt;strong&gt;scalability&lt;/strong&gt;. By adopting these technologies, you and your team can feel empowered to deliver high-quality software faster and more reliably.&lt;/p&gt;

&lt;h2&gt;
  
  
  BONUS!
&lt;/h2&gt;

&lt;p&gt;Containerisation is available in different flavours so-to-speak, however, Docker, along with Docker Desktop and its offering is what I would highly recommend if you are doing anything commercially; if you are just doing something yourself you can get away without it. However, if purely personal and not commercial you can still use Docker Desktop without any cost!&lt;/p&gt;

&lt;p&gt;The benefits of &lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;, &lt;a href="https://www.docker.com/products/docker-hub/" rel="noopener noreferrer"&gt;Docker Hub&lt;/a&gt; are immense, when you are using base images from external sources you are relying on trust. That's all well and good, however, you can also trust Docker Inc. i.e. if you enable only Docker Official images and Verified Publisher images, you are additionally trusting Docker Inc. as well. I see this as a good thing!&lt;/p&gt;

&lt;p&gt;It's all about securing your software supply chain, every link in that chain you would ideally want to be trusted, verified and more. Docker helps you improve in this area directly. Not only this but Docker Inc, has made some strategic partnerships recently, one to mention is &lt;a href="https://www.chainguard.dev/" rel="noopener noreferrer"&gt;Chainguard&lt;/a&gt;. I won't go into much of that here, but in essence &lt;a href="https://www.chainguard.dev/" rel="noopener noreferrer"&gt;Chainguard&lt;/a&gt; curates minimal, highly-optimised container images and either reduces the open CVE's or gets the CVE count down to zero! Awesome 😎&lt;/p&gt;

&lt;p&gt;One last thing, Docker Inc, has &lt;a href="https://www.docker.com/products/docker-scout/" rel="noopener noreferrer"&gt;Docker Scout&lt;/a&gt; which further improves your software supply chain, there's that key phrase again, it's because it's vitally important and not to be underestimated.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Information
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Docker: &lt;a href="https://www.docker.com" rel="noopener noreferrer"&gt;https://www.docker.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docker Docs: &lt;a href="https://docs.docker.com" rel="noopener noreferrer"&gt;https://docs.docker.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docker Desktop: &lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;https://www.docker.com/products/docker-desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docker Hub: &lt;a href="https://www.docker.com/products/docker-hub/" rel="noopener noreferrer"&gt;https://www.docker.com/products/docker-hub/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docker Scout: &lt;a href="https://www.docker.com/products/docker-scout/" rel="noopener noreferrer"&gt;https://www.docker.com/products/docker-scout/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Chainguard: &lt;a href="https://www.chainguard.dev/" rel="noopener noreferrer"&gt;https://www.chainguard.dev/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Kubernetes: &lt;a href="https://kubernetes.io/" rel="noopener noreferrer"&gt;https://kubernetes.io/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Nomad: &lt;a href="https://www.nomadproject.io/" rel="noopener noreferrer"&gt;https://www.nomadproject.io/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Udemy, Docker Mastery: &lt;a href="https://www.udemy.com/course/docker-mastery/" rel="noopener noreferrer"&gt;https://www.udemy.com/course/docker-mastery/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>softwareengineering</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Engineering Metrics Are Overrated</title>
      <dc:creator>Pete King</dc:creator>
      <pubDate>Wed, 03 Jul 2024 11:17:51 +0000</pubDate>
      <link>https://dev.to/peteking/engineering-metrics-are-overrated-24je</link>
      <guid>https://dev.to/peteking/engineering-metrics-are-overrated-24je</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Engineering metrics are overrated, I don't think so! I think they are vitally important and a valuable tool for gauging the health and progress of software engineering teams.&lt;/p&gt;

&lt;p&gt;While some argue that metrics can be misleading or misused, they provide crucial data points that can inform decision-making and identify areas for improvement.&lt;/p&gt;

&lt;p&gt;We all know software development is a complex process with numerous moving parts. Measurement is a key way to quantify these parts.&lt;/p&gt;

&lt;h2&gt;
  
  
  DORA
&lt;/h2&gt;

&lt;p&gt;DevOps Research and Assessment, DORA for short, these metrics focus on outcomes that directly impact software delivery.&lt;/p&gt;

&lt;p&gt;However, DORA metrics are &lt;strong&gt;&lt;em&gt;lagging&lt;/em&gt;&lt;/strong&gt; indicators, meaning they reveal information after the fact. To gain more real-time insights into development health, &lt;strong&gt;&lt;em&gt;leading&lt;/em&gt;&lt;/strong&gt; indicators are essential.&lt;/p&gt;

&lt;p&gt;For some quick insight into DORA and its Core Model, please visit &lt;a href="https://dev.to/peteking/dora-is-more-than-dora-22ic"&gt;DORA is More Than DORA&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leading indicators 📈
&lt;/h2&gt;

&lt;p&gt;Here are some leading indicators that we commonly use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pull Request (PR) size:&lt;/strong&gt; Smaller PR's are generally easier to review and merge, leading to fast deployment cycles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pull Request (PR) review time:&lt;/strong&gt; Faster PR review times indicate smoother collaboration and knowledge sharing within the team, which can lead to faster deployment cycles; similar to above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code coverage:&lt;/strong&gt; Measures the percentage of code executed by automated tests. High code coverage indicates a lower likelihood of undetected bugs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code churn:&lt;/strong&gt; Tracks the amount of code that is added, deleted, or modified. Low churn suggests a more stable codebase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code complexity:&lt;/strong&gt; Complex code can be difficult to understand and maintain, leading to higher development costs and increased risk of errors. By monitoring code complexity, teams can identify areas for refactoring to improve code quality.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...and there are many more &lt;em&gt;leading&lt;/em&gt; indicators that you can add to your team toolbox to continually monitor and improve 😎&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I get these metrics?
&lt;/h2&gt;

&lt;p&gt;Well that's the hard yet can be easy part, you could build your own integrations and all, crunch this data and present them, leverage open source, however, I would guess this is not your core business. Therefore, I'd argue purchasing a SaaS platform off-the-shelf is the easy way. You'll just need to evaluate the market, ensure it fits your needs and your toolchain etc. There is some open source out there (&lt;a href="https://devlake.apache.org/"&gt;Apache DevLake&lt;/a&gt;) in this space, I'd encourage you to take a look and see if it suits your needs, wants desires.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The key to using software engineering metrics effectively lies in selecting the right metrics for the specific product/project goals and context. It's also crucial to avoid relying solely on metrics to make decisions, remember, these are data points. Metrics should be used in conjunction with other factors such as team feedback and code reviews to get a holistic view of the team's development process; i.e. data-driven decisions for continuous improvement.&lt;/p&gt;

&lt;p&gt;It's not just about lagging metrics such as DORA, but leading metrics for you and your team to monitor, understand, adjust how you go about software engineering and delivering on agreed targets &amp;amp; outcomes 🎯&lt;/p&gt;

&lt;h2&gt;
  
  
  More information
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;DORA is More Than DORA: &lt;a href="https://dev.to/peteking/dora-is-more-than-dora-22ic"&gt;https://dev.to/peteking/dora-is-more-than-dora-22ic&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DORA: &lt;a href="https://dora.dev"&gt;https://dora.dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SaaS Platforms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;LinearB: &lt;a href="https://linearb.io/"&gt;https://linearb.io/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sleuth: &lt;a href="https://www.sleuth.io/"&gt;https://www.sleuth.io/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Swarmia: &lt;a href="https://www.swarmia.com/"&gt;https://www.swarmia.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Jellyfish: &lt;a href="https://jellyfish.co/"&gt;https://jellyfish.co/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Haystack: &lt;a href="https://www.usehaystack.io/"&gt;https://www.usehaystack.io/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AllStacks: &lt;a href="https://www.allstacks.com/"&gt;https://www.allstacks.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Apache DevLake (Open Source): &lt;a href="https://devlake.apache.org/"&gt;https://devlake.apache.org/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The above is not an extensive list&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>productivity</category>
      <category>softwaredevelopment</category>
      <category>performance</category>
    </item>
    <item>
      <title>DORA is More Than DORA</title>
      <dc:creator>Pete King</dc:creator>
      <pubDate>Thu, 27 Jun 2024 15:38:09 +0000</pubDate>
      <link>https://dev.to/peteking/dora-is-more-than-dora-22ic</link>
      <guid>https://dev.to/peteking/dora-is-more-than-dora-22ic</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;DORA you hear me say, what's that, and you may already know? Let's take a brief moment to summarise. Nothing to do with Dory I'm afraid, sorry!&lt;/p&gt;

&lt;p&gt;First of all, what does DORA stand for? DevOps Research and Assessment, it's a research programme and more, it seeks to understand the capabilities that drive software delivery and operations performance. The data it gathers through its research programme helps teams apply capabilities, leading to better organisational performance.&lt;/p&gt;

&lt;p&gt;It's a big undertaking and DORA reports go back to 2014, useful to see trends as well as understand some history.&lt;/p&gt;

&lt;p&gt;DORA started as a team at Google Cloud and focused on understanding DevOps performance by using data; metrics. The ultimate goals was to improve performance and collaboration whilst continuing to drive velocity. These metrics as used as a continuous improvement mechanism, teams can understand their current performance and set goals to progress against them.&lt;/p&gt;

&lt;h2&gt;
  
  
  4-Key Metrics
&lt;/h2&gt;

&lt;p&gt;If you have heard of DORA, you may have heard of the 4-key metrics.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deployment frequency&lt;/li&gt;
&lt;li&gt;Lead time for changes&lt;/li&gt;
&lt;li&gt;Change failure rate&lt;/li&gt;
&lt;li&gt;Time to restore service&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Deployment Frequency:&lt;/strong&gt; Measure how often code is successfully released to production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lead Time for Changes:&lt;/strong&gt; Measures the amount of time it takes a code change to be committed to production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change Failure Rate:&lt;/strong&gt; Measures the percentage of deployments that result in a failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time to Restore Service:&lt;/strong&gt; Measure the time it takes to restore service after a deployment failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  DORA Core Model
&lt;/h2&gt;

&lt;p&gt;Now we've had a general overview, those who have heard about DORA or have even applied it previously may understand those metrics, the journey usually stops there. Teams focus on the 4-key metrics, track where they are, set targets to achieve; hopefully leads to an increase in overall performance, collaboration and velocity.&lt;br&gt;
You'd think that is it... but it isn't.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;DORA is more than DORA!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;DORA's research programme formulated a model known as DORA Core.&lt;br&gt;
Their research team applied behavioural science methodology to uncover the predictive pathways which connect ways of working, via software delivery performance, to organisational goals and individual well-being.&lt;/p&gt;

&lt;p&gt;You can checkout the &lt;a href="https://dora.dev/research/" rel="noopener noreferrer"&gt;DORA Core Model&lt;/a&gt; here: &lt;a href="https://dora.dev/research/" rel="noopener noreferrer"&gt;https://dora.dev/research/&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;a href="https://dora.dev/research/" rel="noopener noreferrer"&gt;DORA Core Model&lt;/a&gt; is a collection of capabilities, metrics, and outcomes that represent the most firmly-established findings from across the history and breadth of DORA’s research programme. Core is derived from DORA’s ongoing research, including the analyses presented in their annual Accelerate State of DevOps Reports. Core is intended to be used as a guide in practitioner contexts: it deliberately trails the research, evolving more conservatively. The concepts and relationships shown in the Core Model have been repeatedly demonstrated by their research, and have been successfully used by software engineering teams to prioritise continuous improvement.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3allcchlgzb7ineih48b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3allcchlgzb7ineih48b.png" alt="DORA Core Model Diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is an interactive diagram, I encourage you to checkout: &lt;a href="https://dora.dev/research/" rel="noopener noreferrer"&gt;DORA Core Model&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;What does this actually convey? Well, there are a number of capabilities, mostly technical capabilities on the left. You can see how elements contribute to other elements, for instance &lt;strong&gt;Documentation quality&lt;/strong&gt; predicts a whole range of technical capabilities it impacts, this range of technical capabilities contributes to &lt;strong&gt;Shift left security&lt;/strong&gt; and &lt;strong&gt;Continuous delivery&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Continuous delivery helps cultivate a &lt;strong&gt;Generative organisational culture&lt;/strong&gt;, and if and when you have &lt;strong&gt;Streamlined change approval&lt;/strong&gt; you'll be able to predict your &lt;strong&gt;Software Delivery Performance&lt;/strong&gt; (DORA Metrics). &lt;/p&gt;

&lt;p&gt;Software Delivery Performance, the 4-key metrics are important but are not the be all and end all, i.e. we are software engineering professionals and we have a great deal to do!&lt;/p&gt;

&lt;p&gt;As software engineering professionals we care about all the things on the left, including software delivery performance so much that we can at times forget about outcomes. These are vitally important, whatever software product or software capability we are producing there is always a reason, and these metrics predict it will drastically impact an organisation, whether commercially or non-commercially.&lt;/p&gt;

&lt;p&gt;Commercial / non-commercial examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Profitability&lt;/li&gt;
&lt;li&gt;Productivity&lt;/li&gt;
&lt;li&gt;Market share&lt;/li&gt;
&lt;li&gt;Number of customers&lt;/li&gt;
&lt;li&gt;Quantity of products or services&lt;/li&gt;
&lt;li&gt;Operating efficiency&lt;/li&gt;
&lt;li&gt;Customer satisfaction&lt;/li&gt;
&lt;li&gt;Quality of products or services provided&lt;/li&gt;
&lt;li&gt;Achieving organisation or mission goals&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Well-being
&lt;/h3&gt;

&lt;p&gt;I like to think that happy people are more productive people, no matter what the profession, software engineering is no exception to this. The well-being of your people, your team is always paramount.&lt;/p&gt;

&lt;p&gt;When we have less deployment pain, less rework, less burnout, we have a greater chance of success which leads towards achieving our organisational &lt;strong&gt;outcomes&lt;/strong&gt;. Trace those lines back in the &lt;a href="https://dora.dev/research/" rel="noopener noreferrer"&gt;DORA Core Model&lt;/a&gt; and you'll see &lt;strong&gt;Continuous delivery&lt;/strong&gt; and &lt;strong&gt;Streamlined change approval&lt;/strong&gt; will likely predict the outcomes of Well-being; follow the lines back and you'll see more signs as per the diagram, clear isn't it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;I wanted to keep this somewhat short so you can explore more about the model yourself, the title of this article says a lot, DORA is more than DORA, the misconception around it's just 4-key metrics and sometimes no one else around you may care about those metrics either!&lt;/p&gt;

&lt;p&gt;DORA is so much more, the &lt;a href="https://dora.dev/research/" rel="noopener noreferrer"&gt;DORA Core Model&lt;/a&gt; can really help you understand the landscape better, giving you tools, guidance and advice to ultimately achieve the desired for your organisation is seeking. Furthermore, it doesn't stop with DORA's 4-key metrics and its core model either, there are other metrics that can be somewhat leading indicators that can help any software delivery team improve their performance, collaboration and velocity - I may just cover these in a separate article. Stay tuned!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is a new DORA Core Model V2 available - &lt;a href="https://dora.dev/core-v2/" rel="noopener noreferrer"&gt;https://dora.dev/core-v2/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I hope that by reading this you have learnt a little something and are more curious about DORA.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Information
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;DORA - &lt;a href="https://dora.dev/" rel="noopener noreferrer"&gt;https://dora.dev/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DORA Research (DORA Core Model) - &lt;a href="https://dora.dev/research/" rel="noopener noreferrer"&gt;https://dora.dev/research/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DORA Capabilities Catalogue - &lt;a href="https://dora.dev/devops-capabilities/" rel="noopener noreferrer"&gt;https://dora.dev/devops-capabilities/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DORA Guides - &lt;a href="https://dora.dev/guides/" rel="noopener noreferrer"&gt;https://dora.dev/guides/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DORA Community of Practice - &lt;a href="https://dora.community/" rel="noopener noreferrer"&gt;https://dora.community/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>productivity</category>
      <category>softwaredevelopment</category>
      <category>performance</category>
    </item>
    <item>
      <title>API's From Dev to Production - Part 11 - Pulumi - IAC</title>
      <dc:creator>Pete King</dc:creator>
      <pubDate>Mon, 07 Jun 2021 16:30:20 +0000</pubDate>
      <link>https://dev.to/peteking/api-s-from-dev-to-production-part-11-pulumi-3pmk</link>
      <guid>https://dev.to/peteking/api-s-from-dev-to-production-part-11-pulumi-3pmk</guid>
      <description>&lt;h2&gt;
  
  
  Series Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to &lt;strong&gt;Part 11&lt;/strong&gt; of this blog series that will go from the most basic example of a .net 5 webapi in C#, and the journey from development to production with a &lt;a href="https://dev.to/peteking/shift-left-engineering-4fpp"&gt;&lt;strong&gt;shift-left&lt;/strong&gt;&lt;/a&gt; mindset. We will use &lt;strong&gt;Azure&lt;/strong&gt;, &lt;strong&gt;Docker&lt;/strong&gt;, &lt;strong&gt;GitHub&lt;/strong&gt;, &lt;strong&gt;GitHub Actions&lt;/strong&gt; for CI/C-Deployment and Infrastructure as Code using &lt;strong&gt;Pulumi&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this post we will be looking at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Infrastructure as Code

&lt;ul&gt;
&lt;li&gt;Pulumi&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;We got to grips with IaC and made use of our C# skills to build our own infrastructure - Go shift-left! Pulumi has been a joy to use, just so easy, the integration of GitHub Actions and Pulumi has equally been awesome.&lt;/p&gt;

&lt;p&gt;We were able to preview our infrastructure changes from a Pull Request using our CI Workflow and achieved C-Deployment by deploying our infrastructure upon merging into &lt;code&gt;main&lt;/code&gt; using our CD Workflow.&lt;/p&gt;

&lt;p&gt;The Pulumi GitHub Bot works great and the Pulumi Console is simply invaluable. 🤓&lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/peteking" rel="noopener noreferrer"&gt;
        peteking
      &lt;/a&gt; / &lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-11" rel="noopener noreferrer"&gt;
        Samples.WeatherForecast-Part-11
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This repository is part of the blog post series, API's from Dev to Production - Part 11 on dev.to. Based on the standard .net standard Weather API sample.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;We will be picking-up where we left off in &lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-10-9j1"&gt;Part 10&lt;/a&gt;, which means you’ll need the end-result from &lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-10" rel="noopener noreferrer"&gt;GitHub Repo - Part 10&lt;/a&gt; to start with.&lt;/p&gt;

&lt;p&gt;If you have followed this series all the way through, and I would encourage you to do so, but it isn't necessary if previous posts are knowledge to you already.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't forget to ensure you have setup Code Climate Quality with your repository.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want to follow along from the code from Part 10, that is really ideal, I have everything in order and it should all work fine. If however, you want to skip to the end and run it all, you can too, but there there are some things you'll need to do. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the GitHub Repo (Part-11) into your own GitHub&lt;/li&gt;
&lt;li&gt;Install/Obtain Access to Azure CLI&lt;/li&gt;
&lt;li&gt;Install Pulumi CLI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Navigate&lt;/em&gt;&lt;/strong&gt; → &lt;code&gt;./src/Samples.Weatherforecast.Infra&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Follow the instructions in this post about: Create a Service Principal&lt;/li&gt;
&lt;li&gt;Follow the instructions in this post about: Securely store Azure Service Principal credentials&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Execute&lt;/em&gt;&lt;/strong&gt; → &lt;code&gt;pulumi preview&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This should create the Pulumi project and its stack.&lt;/p&gt;




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

&lt;p&gt;This post is all about Infrastructure as Code, we want to be more autonomous to build and manage our own infrastructure instead of being &lt;em&gt;fully&lt;/em&gt; dependant on others. Infrastructure as Code has been around a while and there are many solutions, each cloud provider such as Microsoft Azure, AWS, GCP and more all have their own native solutions, and of course the number one name for some time has been Hashicorp's Terraform (OSS, Cloud, Enterprise). However, they all have something in common... they are all in their own languages, from declarative to DSL's and JSON. Using DSL's can be cumbersome as is learning a new language; however, a purpose built language &lt;em&gt;could&lt;/em&gt; have some advantages. Pulumi is one of those solutions that takes a different angle, the ability to get to your end goal of having your infrastructure written as code but in a language you use all the time.&lt;/p&gt;

&lt;p&gt;In this post we will dive head-first into, Pulumi!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Let's get started.&lt;/em&gt; 🤘&lt;/p&gt;




&lt;h2&gt;
  
  
  Microsoft Azure
&lt;/h2&gt;

&lt;p&gt;Get access to Azure, this is kind of mandatory, we will be using Microsoft Azure - If however, you don't have an account, you can get one for FREE! 😁&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://azure.microsoft.com/en-gb/free/" rel="noopener noreferrer"&gt;Sign-up&lt;/a&gt; to Microsoft Azure.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Install/Obtain access to Azure CLI
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-windows?tabs=azure-cli" rel="noopener noreferrer"&gt;Azure CLI on your OS&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OR&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/cli/azure/run-azure-cli-docker" rel="noopener noreferrer"&gt;Azure CLI in Docker&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OR&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/cloud-shell/quickstart" rel="noopener noreferrer"&gt;Azure Cloud Shell in BASH&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OR&lt;/p&gt;

&lt;p&gt;If you already have &lt;a href="https://chocolatey.org/install" rel="noopener noreferrer"&gt;chocolately&lt;/a&gt; installed, you can simply execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;choco install azure-cli
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;As long as you can get access, that's all we need, so use whichever method you prefer.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Pulumi
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What is Pulumi?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.pulumi.com" rel="noopener noreferrer"&gt;Pulumi&lt;/a&gt; is a modern infrastructure as code platform built for engineers; developers and infrastructure.&lt;/p&gt;

&lt;p&gt;The beauty of the product is you can build, deploy and manage your applications and infrastructure using languages and tools you are familiar with. Follow your existing engineering practices and guidelines, and take advantage of the same toolchain you use today.&lt;/p&gt;

&lt;p&gt;This means if you &lt;em&gt;major&lt;/em&gt; in C# for instance, you have all the power of the C# language and all your existing tools. It means you don't need to learn a bespoke language just to manage and provision your infrastructure = Learning curve is minimised.&lt;/p&gt;

&lt;p&gt;When you deploy applications, everyone can work together, or you can choose to be isolated; maybe I'm trying something out or I have something very unique. You can work together and share infrastructure packages no matter what language it is written in, deploy with confidence by validating beforehand and automate it all!&lt;/p&gt;

&lt;p&gt;If that wasn't enough, with Pulumi you can use policies to provide guardrails for teams, allowing them to feel empowered with autonomy (shift-left), and with the Pulumi Console you can see a full audit history. This is known as Policy as Code.&lt;/p&gt;

&lt;p&gt;Pulumi is a breath of fresh air compared to other solutions, now I know what you're thinking, Terraform... Yes, I love it too, and I've worn that T-shirt, it's a good one, everyone has their go-to favourite T. However, I have a preference for Pulumi, its simplicity by using the same language I'm building API's in, whether that is C#, Go, JS/TS, Python etc. The Terraform T-shirt is now washed, ironed, and in the cupboard for a while. Luckily Pulumi can connect to Terraform also, so even if you have a centralised Platform team building foundational infrastructure, initial virtual networking topology, firewalls, SIEM etc., engineering teams can shift-left to build and manage their own components whilst using the outputs of Terraform. If for example I needed the output of a VNET from Terraform, Pulumi can get that and then I can provision my product using Pulumi as a pretty-well autonomous engineering team.&lt;/p&gt;


&lt;h3&gt;
  
  
  Sign-up
&lt;/h3&gt;

&lt;p&gt;Pulumi has a free tier and they say it's &lt;a href="https://www.pulumi.com/pricing?_ga=2.230476764.1724642515.1621867711-1194162379.1621867711#community-edition" rel="noopener noreferrer"&gt;free forever&lt;/a&gt; (for individual use) 😁&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://app.pulumi.com/signup" rel="noopener noreferrer"&gt;Sign-up&lt;/a&gt; to Pulumi.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Pulumi CLI
&lt;/h3&gt;

&lt;p&gt;Pulumi is controlled primarily using its command line interface (CLI). It works with the Pulumi service to deploy changes to cloud apps and infrastructure. It also keeps a history of who updated what in your team and when; which is awesome! The CLI has been designed for productivity and ultrafast feedback, in addition to the ability to use it in CI/CD scenarios.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Install &lt;a href="https://www.pulumi.com/docs/get-started/install/" rel="noopener noreferrer"&gt;Pulumi CLI&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(Again) If you already have &lt;a href="https://chocolatey.org/install" rel="noopener noreferrer"&gt;chocolately&lt;/a&gt; installed, you can simply execute:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;choco install pulumi
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Requirements check
&lt;/h2&gt;

&lt;p&gt;✅ Everything we've done previously, i.e. &lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-10-9j1"&gt;Part 10&lt;/a&gt; etc.&lt;br&gt;
✅ Azure account&lt;br&gt;
✅ Azure CLI - Version used &lt;code&gt;v2.24.0&lt;/code&gt;&lt;br&gt;
✅ Pulumi account (Console)&lt;br&gt;
✅ Pulumi CLI - Version used &lt;code&gt;v2.14.0&lt;/code&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Let's get started with Pulumi!
&lt;/h2&gt;

&lt;p&gt;Fire-up your console of choice and &lt;strong&gt;&lt;em&gt;navigate&lt;/em&gt;&lt;/strong&gt; to the &lt;code&gt;src&lt;/code&gt; directory of the WeatherAPI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Create&lt;/em&gt;&lt;/strong&gt; a folder called, &lt;code&gt;Samples.WeatherForecast.Infra&lt;/code&gt; and &lt;strong&gt;&lt;em&gt;navigate to it&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;mkdir Samples.WeatherForecast.Infra
cd Samples.WeatherForecast.Infra
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Inside&lt;/strong&gt; the &lt;code&gt;infra&lt;/code&gt; directory - &lt;strong&gt;&lt;em&gt;Create&lt;/em&gt;&lt;/strong&gt; a new Pulumi Project.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;pulumi new azure-csharp
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;From here, you'll be prompted by the Pulumi CLI for some values.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;project name:&lt;/strong&gt; &lt;code&gt;Samples.WeatherForecast.Infra&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;project description:&lt;/strong&gt; &lt;code&gt;Infra for WeatherForecast&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;stack name:&lt;/strong&gt; &lt;code&gt;dev&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;azure-native:location: The Azure location to use:&lt;/strong&gt; &lt;code&gt;uksouth&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've chosen &lt;code&gt;UK South&lt;/code&gt; given my location, for you, please choose whatever suits your needs best.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ TIP&lt;br&gt;
If you don't know what locations are available, below is a very quick &amp;amp; easy way to find out.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;az login

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Once login successful, execute
&lt;span class="go"&gt;az account list-locations --output table
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;&lt;em&gt;Open VS Code&lt;/em&gt;&lt;/strong&gt; by executing &lt;code&gt;code .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You should see a nice, brand new Pulumi C# program! 😄&lt;/p&gt;

&lt;p&gt;If you also login to the Pulumi Console, you should see your project there too!&lt;/p&gt;

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



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


&lt;h2&gt;
  
  
  Our API Infrastructure
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx66q7hztfwukd7xq2pth.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx66q7hztfwukd7xq2pth.png" alt="blog-post-11-arch"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Steps
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Step 1
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Open&lt;/em&gt;&lt;/strong&gt; VS Code for the &lt;code&gt;WeatherForecast-API&lt;/code&gt; if you haven't already, and &lt;strong&gt;&lt;em&gt;Navigate&lt;/em&gt;&lt;/strong&gt; → &lt;code&gt;/src/WeatherForecastApi-Infra/&lt;/code&gt;&lt;/p&gt;


&lt;h4&gt;
  
  
  Step 2
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Rename&lt;/em&gt;&lt;/strong&gt; &lt;code&gt;MyStack.cs&lt;/code&gt; to &lt;code&gt;AzureStack.cs&lt;/code&gt;&lt;/p&gt;


&lt;h4&gt;
  
  
  Step 3
&lt;/h4&gt;

&lt;p&gt;Inside &lt;code&gt;AzureStack.cs&lt;/code&gt;, &lt;strong&gt;&lt;em&gt;replace&lt;/em&gt;&lt;/strong&gt; all code with the following&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Pulumi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Pulumi.AzureNative.Resources&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Pulumi.AzureNative.Web&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Pulumi.AzureNative.Web.Inputs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AzureStack&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Stack&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AzureStack&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Obtain our docker image from config&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dockerImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"docker-image"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Resource Group&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rg&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ResourceGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rg-weatherforecastapi-uks-"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// AppService Plan&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;appServicePlan&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AppServicePlan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"appplan-weatherforecastapi-uks-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AppServicePlanArgs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ResourceGroupName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Kind&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Linux"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Reserved&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Sku&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;SkuDescriptionArgs&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"B1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Tier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"BASIC"&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="c1"&gt;// WebApp for Containers&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;WebApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app-weatherforecastapi-uks-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;WebAppArgs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
        &lt;span class="p"&gt;{&lt;/span&gt; 
            &lt;span class="n"&gt;ResourceGroupName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ServerFarmId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;appServicePlan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;SiteConfig&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;SiteConfigArgs&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;AppSettings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; 
                &lt;span class="p"&gt;{&lt;/span&gt; 
                    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;NameValuePairArgs&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"WEBSITES_ENABLE_APP_SERVICE_STORAGE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"false"&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;NameValuePairArgs&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DOCKER_REGISTRY_SERVER_URL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://ghcr.io"&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;NameValuePairArgs&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"WEBSITES_PORT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"8080"&lt;/span&gt; &lt;span class="c1"&gt;// Our custom image exposes port 8080. Adjust for your app as needed.&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;AlwaysOn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;LinuxFxVersion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"DOCKER|&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;dockerImage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;HttpsOnly&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"https://&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultHostName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/weatherforecast"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HealthEndpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"https://&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultHostName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/health"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Endpoint&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;HealthEndpoint&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I appreciate there is a chunk of code there, so let's break it down just a little.&lt;/p&gt;

&lt;p&gt;The first couple of lines is simply getting the config from Pulumi. I have chosen the &lt;code&gt;Require&lt;/code&gt; method because I would rather it fails miserably if the config setting is not present; without it, we shouldn't continue because we'll have no container image. 😆&lt;/p&gt;

&lt;p&gt;Up next is the resource group creation, note that we do not actually specify its &lt;em&gt;location&lt;/em&gt; - The location comes from the config file - &lt;code&gt;Pulumi.dev.yaml&lt;/code&gt;, it's value is from, &lt;code&gt;azure-native:location&lt;/code&gt;. 😉&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There are a bunch of other Azure native settings, for more information on these, please see, &lt;a href="https://www.pulumi.com/docs/intro/cloud-providers/azure/#configuration" rel="noopener noreferrer"&gt;Azure Native Configuration&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Following-on from there is the creation of the Azure AppService Plan, this is essentially our underlying Virtual Machine (VM), I have picked the smallest available WebApp for Containers (Linux) I can; so if you are paying for this I recommend when you test, to destroy afterwards - Still, if you didn't, it would be a under GBP£10 per month anyway.&lt;/p&gt;

&lt;p&gt;Then we have the WebApp itself, this is the &lt;em&gt;AppService&lt;/em&gt;, there are a few things we need to set here, mainly stating where the Docker registry is, in our case, GitHub Container Registry - Please note that the default is always DockerHub, you can easily point it to ACR (Azure Container Registry) also. We set &lt;code&gt;AlwaysOn&lt;/code&gt; and &lt;code&gt;HttpsOnly&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; and we of course need to set the Docker Image, this is the, &lt;code&gt;LinuxFxVersion&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;We need a way to output values, we want to output the address of the host as the end of the resource names will be dynamically generated. We set one for the &lt;code&gt;weatherforecast&lt;/code&gt; and just to be helpful we set one for the &lt;code&gt;health&lt;/code&gt; endpoint too.&lt;/p&gt;

&lt;p&gt;Finally, the last 2 lines of the file are the declarations of the output properties, what denotes these as outputs is simply the 'Output' attribute.&lt;/p&gt;


&lt;h4&gt;
  
  
  Step 4
&lt;/h4&gt;

&lt;p&gt;Modify the &lt;code&gt;Pulumi.dev.yaml&lt;/code&gt; file.&lt;br&gt;
&lt;strong&gt;&lt;em&gt;Replace&lt;/em&gt;&lt;/strong&gt; its contents with the below:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;config:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;azure-native:location:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;uksouth&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;Samples.WeatherForecast.Infra:docker-image:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ghcr.io/peteking/samples-weatherforecast-part&lt;/span&gt;&lt;span class="mi"&gt;-11&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;dae&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="err"&gt;cc&lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="err"&gt;eff&lt;/span&gt;&lt;span class="mi"&gt;6861687444&lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="mi"&gt;6e91&lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="mi"&gt;89&lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="err"&gt;bed&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="err"&gt;f&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We've added our &lt;em&gt;own config&lt;/em&gt; here which is our docker image, therefore, it's configuration instead of C# code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This should be the location of your Docker image.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're using the GitHub Container Registry, it should be a simple swap.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h4&gt;
  
  
  Step 5
&lt;/h4&gt;

&lt;p&gt;We have enough to give it a good manual "interactive" test with the Pulumi CLI, just to make sure everything is working as we expect it to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open&lt;/strong&gt; your terminal and &lt;strong&gt;&lt;em&gt;Navigate&lt;/em&gt;&lt;/strong&gt; → &lt;code&gt;/src/WeatherForecastApi-Infra/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Execute&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;p&gt;Here you can see it has executed &lt;code&gt;preview&lt;/code&gt; as part of the &lt;code&gt;up&lt;/code&gt; command, it highlights what it plans to do, in this case, it will &lt;strong&gt;create 4 resources&lt;/strong&gt;.&lt;/p&gt;

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



&lt;p&gt;&lt;strong&gt;Select&lt;/strong&gt; → &lt;em&gt;yes&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once you've selected &lt;em&gt;yes&lt;/em&gt;, Pulumi will go-ahead and &lt;em&gt;create&lt;/em&gt; those resources, below is the response.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;You can see the Pulumi &lt;code&gt;outputs&lt;/code&gt; we created, one that links to the &lt;code&gt;weatherforecast&lt;/code&gt; endpoint and one to the &lt;code&gt;health&lt;/code&gt; endpoint; we've done this because the resources are dynamic, i.e. at the end of our Azure Resources is a random string to ensure uniqueness. If this doesn't suit your use case, you can ensure consistency instead - For more information, see &lt;a href="https://www.pulumi.com/docs/intro/concepts/resources/#autonaming" rel="noopener noreferrer"&gt;Pulumi - Autonaming&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;We now have the opportunity to test our cloud infrastructure as code deployment and see if our API works!&lt;/p&gt;

&lt;p&gt;You can use the links from the &lt;code&gt;output&lt;/code&gt; to save typing.&lt;/p&gt;

&lt;p&gt;The weather forecast gets returned! 🎉&lt;/p&gt;

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



&lt;p&gt;Even the &lt;code&gt;/health&lt;/code&gt; endpoint is reporting good news! 🎆&lt;/p&gt;

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



&lt;p&gt;If you want to minimise your costs 💲, you can of course &lt;em&gt;destroy&lt;/em&gt; 💥 your resources, and with the powers of Pulumi that is also a breeze with the, &lt;code&gt;destory&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Execute&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi destory
&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/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpnkktp4f54fsoeqejaul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpnkktp4f54fsoeqejaul.png" alt="blog-11-06"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Select&lt;/strong&gt; → &lt;em&gt;yes&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once you've selected &lt;em&gt;yes&lt;/em&gt;, Pulumi will go-ahead and &lt;em&gt;destroy&lt;/em&gt; those resources, below is the output. 👍&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;ℹ️ &lt;br&gt;
You'll notice the output at the bottom stating the &lt;em&gt;stack is still available&lt;/em&gt; - All this means is you have all the details, history and everything about your stack in the Pulumi Console; which is all rather helpful, don't you think?&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  GitHub Actions with Pulumi
&lt;/h2&gt;

&lt;p&gt;All of what we have achieved is great, we have got everything we need infrastructure wise, built it all as code, we've even deployed and tested it for real. However, it's all rather interactive, we need to bake this into both our CI Workflow and our CD Workflow.&lt;/p&gt;

&lt;p&gt;Our CI Workflow will execute &lt;code&gt;pulumi preview&lt;/code&gt; and utilise the Pulumi GitHub Bot for instant feedback in the Pull Request; by doing this, the person reviewing the PR will be able to see what resources will be created/destroyed/updated - If anything is a miss then you have the opportunity to do something about it.&lt;/p&gt;

&lt;p&gt;Our CD Workflow will execute &lt;code&gt;pulumi up&lt;/code&gt; and deploy our resources to Azure; but only once all our tests pass, code coverage pass and our container scan is all good too.&lt;/p&gt;

&lt;p&gt;In order to interact with Azure in GitHub Actions we will create a Service Principal.&lt;/p&gt;


&lt;h3&gt;
  
  
  Azure Service Principal
&lt;/h3&gt;
&lt;h4&gt;
  
  
  What is it?
&lt;/h4&gt;

&lt;p&gt;A Service Principal is an object in a single tenant or directory in Azure AD. It is the identity of an application instance, it defines access for the application and what resources it can access. Instead of using a &lt;em&gt;user&lt;/em&gt; identity, we can create a &lt;em&gt;service&lt;/em&gt; identity and manage its security and access.&lt;/p&gt;


&lt;h3&gt;
  
  
  Create Azure Service Principal
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Step 1
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Create&lt;/em&gt;&lt;/strong&gt; an, &lt;em&gt;Azure Subscription&lt;/em&gt; (if you don't already have one).&lt;/p&gt;


&lt;h4&gt;
  
  
  Step 2
&lt;/h4&gt;

&lt;p&gt;Obtain the subscription id:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az account list &lt;span class="c"&gt;#It's the 'id' field.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3
&lt;/h4&gt;

&lt;p&gt;Set the subscription:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az account &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--subscription&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#&amp;lt;id&amp;gt; is the 'id' field from above&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 4
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az ad sp create-for-rbac &lt;span class="nt"&gt;--name&lt;/span&gt; sp-weather-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You should see a result from Step 4 with the following values&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"appId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WWWWWWWW-WWWW-WWWW-WWWW-WWWWWWWWWWWW"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"displayName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ServicePrincipalName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://ServicePrincipalName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tenant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;ℹ️ TIP&lt;br&gt;
If you'd rather it in another format to save time later, you can use the &lt;code&gt;--sdk-auth&lt;/code&gt; parameter.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h3&gt;
  
  
  Securely store Azure Service Principal credentials
&lt;/h3&gt;

&lt;p&gt;Now we have the Service Principal, we need to be able to store the details securely.&lt;/p&gt;

&lt;p&gt;We can store these in GitHub Secrets which is totally fine right, but that means they are not linked to our Pulumi Stack, so another way is to store them inside Pulumi; which can also store &lt;em&gt;secrets&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;From the output of our Service Principal creation command, the following values can be mapped like so:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service Principal Value&lt;/th&gt;
&lt;th&gt;SDK Name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;appId&lt;/td&gt;
&lt;td&gt;clientId&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;password&lt;/td&gt;
&lt;td&gt;clientSecret&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tenant&lt;/td&gt;
&lt;td&gt;tenantId&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;This is where the &lt;code&gt;--sdk-auth&lt;/code&gt; parameter can help, it will automatically map these for you.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h4&gt;
  
  
  Pulumi Secrets ㊙️
&lt;/h4&gt;

&lt;p&gt;By storing these in Pulumi we get multi-user access for deployment which includes using it interactively; if we stored them in GitHub Secrets, we can only use them directly from the pipeline itself; there are pros and cons this of course - Choose what suites you best.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi config &lt;span class="nb"&gt;set &lt;/span&gt;azure-native:clientId &lt;span class="s2"&gt;"WWWWWWWW-WWWW-WWWW-WWWW-WWWWWWWWWWWW"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi config &lt;span class="nb"&gt;set &lt;/span&gt;azure-native:clientSecret &lt;span class="s2"&gt;"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"&lt;/span&gt; &lt;span class="nt"&gt;--secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;❗ Important&lt;br&gt;
Note the &lt;code&gt;--secret&lt;/code&gt; at the end of the command, this is very important; otherwise, the value will be stored in plane text, whereas we want it to be encrypted and stored as a secret.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi config &lt;span class="nb"&gt;set &lt;/span&gt;azure-native:tenantId &lt;span class="s2"&gt;"YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi config &lt;span class="nb"&gt;set &lt;/span&gt;azure-native:subscriptionId &lt;span class="s2"&gt;"ZZZZZZZZ-ZZZZ-ZZZZ-ZZZZ-ZZZZZZZZZZZZ"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;p&gt;Once you have executed these commands, you'll notice your &lt;code&gt;Pulumi.dev.yml&lt;/code&gt; file has been updated accordingly.&lt;/p&gt;


&lt;h3&gt;
  
  
  CI Workflow
&lt;/h3&gt;

&lt;p&gt;Let's modify our &lt;strong&gt;CI&lt;/strong&gt; Workflow &lt;code&gt;./github/workflows/ci-pull-request.yml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Add&lt;/em&gt;&lt;/strong&gt; a new &lt;em&gt;job&lt;/em&gt; at the bottom of the Workflow.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;infra&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ci&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repo&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Disabling shallow clone is recommended for improving relevancy of reporting&lt;/span&gt;
        &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Pulumi CLI&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pulumi/action-install-pulumi-cli@v1.1.0&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set Pulumi config values&lt;/span&gt;
      &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/Samples.WeatherForecast.Infra&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;PULUMI_ACCESS_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.PULUMI_ACCESS_TOKEN }}&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;pulumi stack select dev&lt;/span&gt;
        &lt;span class="s"&gt;pulumi config set Samples.WeatherForecast.Infra:docker-image ${{ env.image-name }}&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pulumi Preview&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pulumi/actions@v3.1.0&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;preview&lt;/span&gt;
        &lt;span class="na"&gt;stack-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
        &lt;span class="na"&gt;work-dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/Samples.WeatherForecast.Infra&lt;/span&gt;
        &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;PULUMI_ACCESS_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.PULUMI_ACCESS_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Where do I get the Pulumi Personal Access Token from?
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Navigate&lt;/em&gt;&lt;/strong&gt; → Pulumi Console - &lt;a href="https://app.pulumi.com" rel="noopener noreferrer"&gt;https://app.pulumi.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Navigate&lt;/em&gt;&lt;/strong&gt; → Your profile (top-right)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Click&lt;/em&gt;&lt;/strong&gt; → &lt;em&gt;Access Tokens&lt;/em&gt; - Quick link here: &lt;a href="https://app.pulumi.com/YOUR_PULUMI_USER_NAME/settings/tokens" rel="noopener noreferrer"&gt;https://app.pulumi.com/YOUR_PULUMI_USER_NAME/settings/tokens&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Click&lt;/em&gt;&lt;/strong&gt; → &lt;em&gt;Create token&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Copy&lt;/em&gt;&lt;/strong&gt; the secret generated&lt;/li&gt;
&lt;li&gt;Safely store the secret in GitHub Actions Secrets with the name of &lt;code&gt;PULUMI_ACCESS_TOKEN&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;h3&gt;
  
  
  Install the Pulumi GitHub Bot
&lt;/h3&gt;

&lt;p&gt;Unless you want to dive into the Actions log to find out how the &lt;code&gt;pulumi preview&lt;/code&gt; went, you better &lt;a href="https://github.com/apps/pulumi" rel="noopener noreferrer"&gt;install the Pulumi GitHub Bot&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.pulumi.com/docs/guides/continuous-delivery/github-app/" rel="noopener noreferrer"&gt;Step by step guide on Pulumi Docs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you have the bot installed, when you initiate a PR, you'll see something like the below:&lt;/p&gt;

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



&lt;p&gt;Even better though, is you can view more details in the Pulumi Console like so:&lt;/p&gt;

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



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


&lt;h3&gt;
  
  
  CD Workflow
&lt;/h3&gt;

&lt;p&gt;Let's modify our &lt;strong&gt;CD&lt;/strong&gt; Workflow &lt;code&gt;./github/workflows/build-and-publish.yml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Add&lt;/em&gt;&lt;/strong&gt; a new &lt;em&gt;job&lt;/em&gt; at the bottom of the Workflow.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;infra&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-and-push&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repo&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Disabling shallow clone is recommended for improving relevancy of reporting&lt;/span&gt;
        &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Pulumi CLI&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pulumi/action-install-pulumi-cli@v1.1.0&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set Pulumi config values&lt;/span&gt;
      &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/Samples.WeatherForecast.Infra&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;PULUMI_ACCESS_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.PULUMI_ACCESS_TOKEN }}&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;pulumi stack select dev&lt;/span&gt;
        &lt;span class="s"&gt;pulumi config set Samples.WeatherForecast.Infra:docker-image ${{ env.image-name }}&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pulumi Up&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pulumi/actions@v3.1.0&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;up&lt;/span&gt;
        &lt;span class="na"&gt;stack-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
        &lt;span class="na"&gt;work-dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/Samples.WeatherForecast.Infra&lt;/span&gt;
        &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;PULUMI_ACCESS_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.PULUMI_ACCESS_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Commit your code
&lt;/h4&gt;

&lt;p&gt;It's time to commit you code, but before you do, let's update our &lt;em&gt;branch protection rule&lt;/em&gt; - Now we have an &lt;code&gt;infra&lt;/code&gt; job in our Workflow which does a &lt;code&gt;pulumi preview&lt;/code&gt;, we most likely want this to be mandatory and pass.&lt;/p&gt;

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



&lt;p&gt;Now that we have our branch protection rules updated and CD Workflow file completed, let's ensure it all works.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Raise your PR and see the CI Workflow kick-off.&lt;/li&gt;
&lt;li&gt;Verify everything is fine by looking at the Pulumi GitHub Bot or the Pulumi Console.&lt;/li&gt;
&lt;li&gt;Wait until all GitHub Status Checks pass and merge your PR into &lt;code&gt;main&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Verify your Workflow is successful.&lt;/li&gt;
&lt;li&gt;Either:

&lt;ul&gt;
&lt;li&gt;Go to the Pulumi Console and see your Activity.&lt;/li&gt;
&lt;li&gt;OR see the output from the &lt;code&gt;infra&lt;/code&gt; job in the CD Workflow.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Try the &lt;em&gt;output&lt;/em&gt; links to verify your API is running.&lt;/li&gt;
&lt;/ol&gt;



&lt;p&gt;I prefer the Pulumi Console, see below, you can see the merge of my branch and the creation of the infrastructure once it was merged into &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;I was then able to validate that both the &lt;code&gt;weatherforecast&lt;/code&gt; and &lt;code&gt;health&lt;/code&gt; endpoints was functional. &lt;/p&gt;


&lt;h2&gt;
  
  
  What have we learned? 🤔
&lt;/h2&gt;

&lt;p&gt;We have learned all about IaC - &lt;strong&gt;I&lt;/strong&gt;nfrastructure &lt;strong&gt;a&lt;/strong&gt;s &lt;strong&gt;C&lt;/strong&gt;ode, and utilised &lt;a href="//htts://www.pulumi.com"&gt;Pulumi&lt;/a&gt; to achieve this - We have leveraged our existing C# skills to shift-left on our infrastructure; therefore, we are now more autonomous.&lt;/p&gt;

&lt;p&gt;We've been able to test (preview) and verify our infrastructure changes during Pull Requests as part of CI, and we have achieved C-Deployment by releasing our infrastructure in our CD Workflow.&lt;/p&gt;

&lt;p&gt;Relief - That's it! 😌 &lt;/p&gt;

&lt;p&gt;If you've made it this far, congratulations and thank you 🤝, I know it's a long post and a lot to take in, but I hope you have found it worthwhile.&lt;/p&gt;

&lt;p&gt;There is unfortunately or fortunately, depending on how you look at it, still a lot to do 😏 So there will be a more coming soon! 🤪&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;UPDATE AUGUST 27TH 2021&lt;/strong&gt;&lt;br&gt;
The below GitHub Issue has now been resolved.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📄 Note&lt;br&gt;
Adding Pulumi to your repo in GitHub will break CodeQL, &lt;del&gt;there is an active GitHub issue open for this.&lt;/del&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/github/codeql-action/issues/544" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Process failed with exit code 100
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#544&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/peteking" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F8613214%3Fv%3D4" alt="peteking avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/peteking" rel="noopener noreferrer"&gt;peteking&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/github/codeql-action/issues/544" rel="noopener noreferrer"&gt;&lt;time&gt;Jun 03, 2021&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;I have failure in the codeql-action, it has failed with an exit code of 100, looking at the docs it says it's likely a bug based on this error code.&lt;/p&gt;
&lt;p&gt;This is an open source sample project, so all open.&lt;/p&gt;
&lt;p&gt;The recent addition made as you can see from the PR is I've added Pulumi for its Azure infrastructure provisioning.&lt;/p&gt;
&lt;p&gt;I think it start to look at Pulumi here:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  Running TRAP import for CodeQL database at /home/runner/work/_temp/codeql_databases/csharp...
  Running TRAP import for CodeQL database at /home/runner/work/_temp/codeql_databases/csharp...
  Pulumi.AzureNative.dll.trap.gz, 9885761: java.lang.OutOfMemoryError: Java heap space
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and then slowly goes wrong and fails, out of memory.&lt;/p&gt;
&lt;p&gt;Here is the codeql workflow run here:
&lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-11/pull/1/checks?check_run_id=2738620747" rel="noopener noreferrer"&gt;https://github.com/peteking/Samples.WeatherForecast-Part-11/pull/1/checks?check_run_id=2738620747&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Part of the log is here for quick reference:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Finalizing csharp
  /opt/hostedtoolcache/CodeQL/0.0.0-20210517/x64/codeql/codeql database finalize --threads=2 /home/runner/work/_temp/codeql_databases/csharp
  Running pre-finalize script /opt/hostedtoolcache/CodeQL/0.0.0-20210517/x64/codeql/csharp/tools/pre-finalize.sh in /home/runner/work/Samples.WeatherForecast-Part-11/Samples.WeatherForecast-Part-11.
  Running pre-finalize script /opt/hostedtoolcache/CodeQL/0.0.0-20210517/x64/codeql/csharp/tools/pre-finalize.sh in /home/runner/work/Samples.WeatherForecast-Part-11/Samples.WeatherForecast-Part-11.
  [2021-06-03 15:44:36] [build-stderr] Scanning for files in /home/runner/work/Samples.WeatherForecast-Part-11/Samples.WeatherForecast-Part-11...
  [2021-06-03 15:44:36] [build-stderr] Scanning for files in /home/runner/work/Samples.WeatherForecast-Part-11/Samples.WeatherForecast-Part-11...
  [2021-06-03 15:44:36] [build-stderr] /home/runner/work/_temp/codeql_databases/csharp: Indexing files in in /home/runner/work/Samples.WeatherForecast-Part-11/Samples.WeatherForecast-Part-11...
  [2021-06-03 15:44:36] [build-stderr] /home/runner/work/_temp/codeql_databases/csharp: Indexing files in in /home/runner/work/Samples.WeatherForecast-Part-11/Samples.WeatherForecast-Part-11...
  [2021-06-03 15:44:36] [build-stderr] /home/runner/work/_temp/codeql_databases/csharp: Running in /home/runner/work/Samples.WeatherForecast-Part-11/Samples.WeatherForecast-Part-11: [/opt/hostedtoolcache/CodeQL/0.0.0-20210517/x64/codeql/xml/tools/index-files.sh, /home/runner/work/_temp/codeql_databases/csharp/working/files-to-index16194344738812882790.list]
  [2021-06-03 15:44:36] [build-stderr] /home/runner/work/_temp/codeql_databases/csharp: Running in /home/runner/work/Samples.WeatherForecast-Part-11/Samples.WeatherForecast-Part-11: [/opt/hostedtoolcache/CodeQL/0.0.0-20210517/x64/codeql/xml/tools/index-files.sh, /home/runner/work/_temp/codeql_databases/csharp/working/files-to-index16194344738812882790.list]
  Running TRAP import for CodeQL database at /home/runner/work/_temp/codeql_databases/csharp...
  Running TRAP import for CodeQL database at /home/runner/work/_temp/codeql_databases/csharp...
  Pulumi.AzureNative.dll.trap.gz, 9885761: java.lang.OutOfMemoryError: Java heap space
  com.semmle.inmemory.trap.TrapScanner.getTokenString(TrapScanner.java:521)
  com.semmle.inmemory.trap.TrapScanner.getLabelValue(TrapScanner.java:578)
  com.semmle.inmemory.trap.TRAPReader.scanOneField(TRAPReader.java:754)
  Pulumi.AzureNative.dll.trap.gz, 9885761: java.lang.OutOfMemoryError: Java heap space
  com.semmle.inmemory.trap.TRAPReader.scanTuple(TRAPReader.java:495)
  com.semmle.inmemory.trap.TrapScanner.getTokenString(TrapScanner.java:521)
  com.semmle.inmemory.trap.TRAPReader.scanTuplesAndLabels(TRAPReader.java:439)
  com.semmle.inmemory.trap.TrapScanner.getLabelValue(TrapScanner.java:578)
  com.semmle.inmemory.trap.TRAPReader.importTuples(TRAPReader.java:376)
  com.semmle.inmemory.trap.TRAPReader.scanOneField(TRAPReader.java:754)
  com.semmle.inmemory.trap.ImportTasksProcessor.process(ImportTasksProcessor.java:172)
  com.semmle.inmemory.trap.TRAPReader.scanTuple(TRAPReader.java:495)
  com.semmle.inmemory.trap.ImportTasksProcessor.lambda$null$1(ImportTasksProcessor.java:129)
  com.semmle.inmemory.trap.TRAPReader.scanTuplesAndLabels(TRAPReader.java:439)
  com.semmle.inmemory.trap.ImportTasksProcessor$$Lambda$234/0x000000010031fc40.accept(Unknown Source)
  com.semmle.util.concurrent.FutureUtils.lambda$null$8(FutureUtils.java:136)
  com.semmle.util.concurrent.FutureUtils$$Lambda$236/0x000000010031f440.get(Unknown Source)
  java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(Unknown Source)
  java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
  java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
  java.base/java.lang.Thread.run(Unknown Source)
  com.semmle.inmemory.trap.TRAPReader.importTuples(TRAPReader.java:376)
  com.semmle.inmemory.trap.ImportTasksProcessor.process(ImportTasksProcessor.java:172)
  com.semmle.inmemory.trap.ImportTasksProcessor.lambda$null$1(ImportTasksProcessor.java:129)
  com.semmle.inmemory.trap.ImportTasksProcessor$$Lambda$234/0x000000010031fc40.accept(Unknown Source)
  com.semmle.util.concurrent.FutureUtils.lambda$null$8(FutureUtils.java:136)
  com.semmle.util.concurrent.FutureUtils$$Lambda$236/0x000000010031f440.get(Unknown Source)
  java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(Unknown Source)
  java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
  java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
  java.base/java.lang.Thread.run(Unknown Source)
  Oops! A fatal internal error occurred.
  Oops! A fatal internal error occurred.
  java.lang.RuntimeException: java.lang.OutOfMemoryError: Java heap space
  java.lang.RuntimeException: java.lang.OutOfMemoryError: Java heap space
    at com.semmle.util.exception.Exceptions.asUnchecked(Exceptions.java:103)
    at com.semmle.inmemory.trap.TrapImporter.run(TrapImporter.java:105)
    at com.semmle.cli2.ql.dataset.ImportCommand.executeSubcommand(ImportCommand.java:98)
    at com.semmle.cli2.picocli.PlumbingRunner.run(PlumbingRunner.java:110)
    at com.semmle.cli2.picocli.SubcommandCommon.runPlumbingInProcess(SubcommandCommon.java:159)
    at com.semmle.cli2.database.FinalizeCommand.executeSubcommand(FinalizeCommand.java:110)
    at com.semmle.cli2.picocli.SubcommandCommon.call(SubcommandCommon.java:448)
    at com.semmle.util.exception.Exceptions.asUnchecked(Exceptions.java:103)
    at com.semmle.inmemory.trap.TrapImporter.run(TrapImporter.java:105)
    at com.semmle.cli2.ql.dataset.ImportCommand.executeSubcommand(ImportCommand.java:98)
    at com.semmle.cli2.picocli.PlumbingRunner.run(PlumbingRunner.java:110)
    at com.semmle.cli2.picocli.SubcommandCommon.runPlumbingInProcess(SubcommandCommon.java:159)
    at com.semmle.cli2.database.FinalizeCommand.executeSubcommand(FinalizeCommand.java:110)
    at com.semmle.cli2.picocli.SubcommandCommon.call(SubcommandCommon.java:448)
    at com.semmle.cli2.picocli.SubcommandMaker.runMain(SubcommandMaker.java:201)
    at com.semmle.cli2.picocli.SubcommandMaker.runMain(SubcommandMaker.java:209)
    at com.semmle.cli2.CodeQL.main(CodeQL.java:96)
  Caused by: java.lang.OutOfMemoryError: Java heap space
    at com.semmle.cli2.picocli.SubcommandMaker.runMain(SubcommandMaker.java:201)
    at com.semmle.cli2.picocli.SubcommandMaker.runMain(SubcommandMaker.java:209)
    at com.semmle.cli2.CodeQL.main(CodeQL.java:96)
  Caused by: java.lang.OutOfMemoryError: Java heap space
  Error: The process '/opt/hostedtoolcache/CodeQL/0.0.0-20210517/x64/codeql/codeql' failed with exit code 100
  Error: The process '/opt/hostedtoolcache/CodeQL/0.0.0-20210517/x64/codeql/codeql' failed with exit code 100
      at Object.toolrunnerErrorCatcher (/home/runner/work/_actions/github/codeql-action/v1/lib/toolrunner-error-catcher.js:79:19)
      at processTicksAndRejections (internal/process/task_queues.js:93:5)
      at async Object.finalizeDatabase (/home/runner/work/_actions/github/codeql-action/v1/lib/codeql.js:414:13)
      at async finalizeDatabaseCreation (/home/runner/work/_actions/github/codeql-action/v1/lib/analyze.js:74:9)
      at async Object.runAnalyze (/home/runner/work/_actions/github/codeql-action/v1/lib/analyze.js:172:5)
      at async run (/home/runner/work/_actions/github/codeql-action/v1/lib/analyze-action.js:50:30)
      at async runWrapper (/home/runner/work/_actions/github/codeql-action/v1/lib/analyze-action.js:96:9)
&lt;/code&gt;&lt;/pre&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/github/codeql-action/issues/544" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;br&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Next up
&lt;/h2&gt;

&lt;p&gt;Part 12 in this series will be about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;More&lt;/strong&gt; Infrastructure as Code with Pulumi

&lt;ul&gt;
&lt;li&gt;Environmental support (multiple Stacks)&lt;/li&gt;
&lt;li&gt;Security

&lt;ul&gt;
&lt;li&gt;What &lt;em&gt;can&lt;/em&gt; we do on the cheap in Azure?&lt;/li&gt;
&lt;li&gt;What &lt;em&gt;should&lt;/em&gt; we do in a more real scenario?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;GitHub Actions

&lt;ul&gt;
&lt;li&gt;Clean-up a little and add Environment capabilities&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  More information
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.azure.com" rel="noopener noreferrer"&gt;https://www.azure.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/" rel="noopener noreferrer"&gt;https://dotnet.microsoft.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.github.com" rel="noopener noreferrer"&gt;https://www.github.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com" rel="noopener noreferrer"&gt;https://www.docker.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com" rel="noopener noreferrer"&gt;https://www.pulumi.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/coverlet-coverage/" rel="noopener noreferrer"&gt;https://github.com/coverlet-coverage/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.codeclimate.com/quality" rel="noopener noreferrer"&gt;https://www.codeclimate.com/quality&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>docker</category>
      <category>csharp</category>
      <category>iac</category>
    </item>
    <item>
      <title>API's From Dev to Production - Part 10 - CodeQL</title>
      <dc:creator>Pete King</dc:creator>
      <pubDate>Fri, 21 May 2021 15:08:05 +0000</pubDate>
      <link>https://dev.to/peteking/api-s-from-dev-to-production-part-10-9j1</link>
      <guid>https://dev.to/peteking/api-s-from-dev-to-production-part-10-9j1</guid>
      <description>&lt;h2&gt;
  
  
  Series Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to &lt;strong&gt;Part 10&lt;/strong&gt; of this blog series that will go from the most basic example of a .net 5 webapi in C#, and the journey from development to production with a &lt;a href="https://dev.to/peteking/shift-left-engineering-4fpp"&gt;&lt;strong&gt;shift-left&lt;/strong&gt;&lt;/a&gt; mindset. We will use &lt;strong&gt;Azure&lt;/strong&gt;, &lt;strong&gt;Docker&lt;/strong&gt;, &lt;strong&gt;GitHub&lt;/strong&gt;, &lt;strong&gt;GitHub Actions&lt;/strong&gt; for CI/C-Deployment and Infrastructure as Code using &lt;strong&gt;Pulumi&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this post we will be looking at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SAST - &lt;strong&gt;S&lt;/strong&gt;tatic &lt;strong&gt;A&lt;/strong&gt;pplication &lt;strong&gt;S&lt;/strong&gt;ecurity &lt;strong&gt;T&lt;/strong&gt;esting is used to secure software by reviewing/scanning the source code of the software to identify sources of vulnerabilities.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;S&lt;/strong&gt;tatic &lt;strong&gt;A&lt;/strong&gt;pplication &lt;strong&gt;S&lt;/strong&gt;ecurity &lt;strong&gt;T&lt;/strong&gt;esting is a very important step in the journey from dev to production. With an abundance of tools out there on the market, there's bound to be one that suits you. With GitHub CodeQL, we can achieve this in little time with little effort.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can increase your chances of finding security vulnerabilities before they reach production&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/peteking" rel="noopener noreferrer"&gt;
        peteking
      &lt;/a&gt; / &lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-10" rel="noopener noreferrer"&gt;
        Samples.WeatherForecast-Part-10
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This repository is part of the blog post series, API's from Dev to Production - Part 10 on dev.to. Based on the standard .net standard Weather API sample.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;We will be picking-up where we left off in &lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-9-43fd"&gt;Part 9&lt;/a&gt;, which means you’ll need the end-result from &lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-9" rel="noopener noreferrer"&gt;GitHub Repo - Part 9&lt;/a&gt; to start with.&lt;/p&gt;

&lt;p&gt;If you have followed this series all the way through, and I would encourage you to do so, but it isn't necessary if previous posts are knowledge to you already.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't forget to ensure you have setup Code Climate Quality with your repository.&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;p&gt;As we have discussed in this blog post series and in &lt;a href="https://dev.to/peteking/shift-left-engineering-4fpp"&gt;shift-left&lt;/a&gt;, &lt;strong&gt;security testing&lt;/strong&gt; is part of actually &lt;a href="https://dev.to/peteking/shift-left-engineering-4fpp"&gt;&lt;em&gt;shifting-left&lt;/em&gt;&lt;/a&gt;! We need to carry out security testing, and of course we want to automate it, and ensure its early on in the process. There are many tools out there in the market that can scan your code (statically); the newest entrant is from an acquisition of Microsoft's GitHub - &lt;strong&gt;CodeQL&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this post we are going to go through the steps on how to add SAST using CodeQL to our API.&lt;/p&gt;




&lt;h2&gt;
  
  
  A bit of history about CodeQL
&lt;/h2&gt;

&lt;p&gt;GitHub acquired &lt;a href="https://semmle.com/" rel="noopener noreferrer"&gt;Semmle&lt;/a&gt; in 2019 for an undisclosed sum, it originally came out of research completed at Oxford University.&lt;/p&gt;

&lt;p&gt;The technology is based around queries and its language CodeQL, the analysis engine enables the query of large codebases to identify code patterns and search for vulnerabilities and their variants.&lt;/p&gt;

&lt;p&gt;GitHub has now baked this into its platform and has simply made it oh so easy 😁&lt;/p&gt;




&lt;h2&gt;
  
  
  Why is SAST so important?
&lt;/h2&gt;

&lt;p&gt;SAST makes discovering common vulnerabilities simple, and more critically, automated. SAST is a really good way of improving overall good-practice among your engineers.&lt;/p&gt;

&lt;p&gt;Usually engineers outnumber security staff and therefore, it can be rather challenging for any company to find the people to perform code reviews, specifically those with a focus on security. SAST tools provide the ability to analyse 100% of the codebase and are certainly faster than manual code reviews. These tools can easily scan millions of lines of code in a matter of minutes, and automatically identify critical vulnerabilities; such as SQL injection, cross-site scripting, and more with a high level of confidence.&lt;/p&gt;

&lt;p&gt;SAST helps integrate security into the early stages of the software development lifecycle and enables the culture of &lt;a href="https://dev.to/peteking/shift-left-engineering-4fpp"&gt;shifting-left&lt;/a&gt;; to detect vulnerabilities in the proprietary code in the design stage or the coding stage when they are relatively easier to mitigate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1
&lt;/h2&gt;

&lt;p&gt;Navigate to your GitHub Repo → Security → Code Scanning Alerts → &lt;strong&gt;&lt;em&gt;Click&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;Set up this workflow&lt;/em&gt; &lt;/p&gt;

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




&lt;h2&gt;
  
  
  Step 2
&lt;/h2&gt;

&lt;p&gt;This will generate a new GitHub Actions Workflow file called, &lt;code&gt;codeql-analysis.yml&lt;/code&gt; - You're free to change the name, but I've kept it the same.&lt;/p&gt;

&lt;p&gt;The intelligent thing here is that GitHub will detect the languages inside your repo, in our case, it's picked-up C#; which is of course correct 🤓&lt;/p&gt;

&lt;p&gt;One thing to note is the &lt;code&gt;cron&lt;/code&gt;, for me, it did generate an invalid value &lt;em&gt;according to GitHub&lt;/em&gt;. The last digit is the &lt;em&gt;day of the week&lt;/em&gt;, &lt;code&gt;0 = Sunday&lt;/code&gt;; for some reason GitHub doesn't like Sunday... In this case, I've changed it to the below, whereby I've set the day of the week to &lt;code&gt;1 = Monday&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;44&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;23&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1'&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/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftm4u2pby96c6p1mt3014.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftm4u2pby96c6p1mt3014.png" alt="blog-10-02"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For more information about the crontab format, please see &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Commit your changes and head on over to the &lt;strong&gt;&lt;em&gt;Actions&lt;/em&gt;&lt;/strong&gt; tab to see it running.&lt;/p&gt;




&lt;p&gt;Here you can see I have a new workflow on the left and it has executed in 4 minutes and 4 seconds!&lt;/p&gt;

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




&lt;p&gt;You'll notice it has done an autobuild; based on its language detection, CodeQL attempts to build your code, its image has loads of the .NET SDK's to cover everything.&lt;/p&gt;

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

&lt;p&gt;It is worth looking through the build output, even just to get a general understanding on what is going on here.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📄 &lt;strong&gt;Autobuild&lt;/strong&gt;&lt;br&gt;
This means it will build our API twice, yes, twice! Is that OK?&lt;br&gt;
&lt;strong&gt;&lt;em&gt;Not ideally&lt;/em&gt;&lt;/strong&gt; but... I have no issues with it at present, it will build in parallel to our container build, the key is to scan the code, anything built is discarded anyway, we don't need it, just the results.&lt;/p&gt;

&lt;p&gt;It is not optimal given we are using Docker containers, however, there &lt;em&gt;is a way&lt;/em&gt; to bake this into Docker, but the documentation is a little light at the moment and requires a decent deep-dive into it - I will most likely investigate it soon, and of course, create a blog post about it.&lt;/p&gt;

&lt;p&gt;For more information about CodeQL in containers, please see &lt;a href="https://docs.github.com/en/code-security/secure-coding/automatically-scanning-your-code-for-vulnerabilities-and-errors/running-codeql-code-scanning-in-a-container" rel="noopener noreferrer"&gt;Running CodeQL code scanning in a container&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 3
&lt;/h2&gt;

&lt;p&gt;We can configure a few things that are not enabled by default such as &lt;a href="https://docs.github.com/en/code-security/secure-coding/configuring-code-scanning#running-additional-queries" rel="noopener noreferrer"&gt;&lt;em&gt;additional queries&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have left the default queries and listed some extensions, in our case, I'm adding &lt;em&gt;extended security&lt;/em&gt; and &lt;em&gt;security and quality&lt;/em&gt; queries, and of course C# 👍&lt;/p&gt;

&lt;p&gt;This configuration simply goes into a yaml file.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Create&lt;/em&gt;&lt;/strong&gt; a folder called, &lt;code&gt;codeql&lt;/code&gt; under the &lt;code&gt;.github&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Copy &amp;amp; paste&lt;/em&gt;&lt;/strong&gt; the following code into a new file called, &lt;code&gt;codeql-config.yml&lt;/code&gt; - no need to commit your changes yet, as there is another change in Step 4.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Code&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;codeql-config.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default"&lt;/span&gt;

&lt;span class="na"&gt;languages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;csharp&lt;/span&gt;

&lt;span class="na"&gt;queries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Extended Security&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security-extended&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Security and Quality&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security-and-quality&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4
&lt;/h2&gt;

&lt;p&gt;We need to reference our new CodeQL configuration file in our Workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;In&lt;/em&gt;&lt;/strong&gt; the &lt;code&gt;Initialize CodeQL&lt;/code&gt; Workflow Step → &lt;strong&gt;&lt;em&gt;Add&lt;/em&gt;&lt;/strong&gt; a new line to its &lt;strong&gt;&lt;em&gt;with&lt;/em&gt;&lt;/strong&gt; clause like so below:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;codeql-analysis.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;config-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./.github/codeql/codeql-config.yml&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/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhrj8evs8flob4qxeedt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhrj8evs8flob4qxeedt.png" alt="blog-10-06"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5 (Extra)
&lt;/h2&gt;

&lt;p&gt;If you're like me and really like branch protection rules and GitHub Status Checks, you'll want to create/modify your branch protection rule to have both your workflows as &lt;em&gt;required to pass before merging&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finally, commit your changes.&lt;/strong&gt;&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Step 6
&lt;/h2&gt;

&lt;p&gt;If you navigate back to &lt;em&gt;Code scanning alerts&lt;/em&gt; under &lt;em&gt;Security&lt;/em&gt;, hopefully you'll see &lt;em&gt;no alerts&lt;/em&gt;!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Here, other tools can publish their results too, by supporting the &lt;a href="https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=sarif]" rel="noopener noreferrer"&gt;open SARIF standard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Which means even if you expand your toolset later, you can have the same GitHub native experience! Pretty cool right?&lt;/p&gt;
&lt;/blockquote&gt;

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




&lt;h2&gt;
  
  
  Step 7 (Extra)
&lt;/h2&gt;

&lt;p&gt;While we are here in the &lt;em&gt;Security&lt;/em&gt; tab, I applaud you to ensure you have &lt;strong&gt;Dependabot&lt;/strong&gt; alerts &lt;strong&gt;on&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What have we learned?
&lt;/h2&gt;

&lt;p&gt;We have very quickly enabled our API to be scanned by GitHub CodeQL, a SAST analysis engine. It's easy and provides a single pane for you to see any security vulnerabilities in your code. If you end up using another product, these products can push their results into the GitHub Security area of your repository; so long as they integrate with GitHub and the &lt;a href="https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=sarif]" rel="noopener noreferrer"&gt;open SARIF standard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We understand that it will build our API again (but in parallel), but this is not a huge, big deal, as we are just after the results, not the build from that workflow.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Security&lt;/strong&gt; should always be part of your engineering process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;D&lt;/strong&gt;ynamic &lt;strong&gt;A&lt;/strong&gt;pplication &lt;strong&gt;S&lt;/strong&gt;ecurity &lt;strong&gt;T&lt;/strong&gt;esting is another form of security testing, the key point is it being dynamic VS static - We won't cover this here, but there is value to be had with the combination of SAST and DAST. I may cover it in another post in the future 😉&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Next up
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-11-pulumi-3pmk"&gt;Part 11&lt;/a&gt; in this series will be about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Infrastructure as Code

&lt;ul&gt;
&lt;li&gt;Pulumi&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  More information
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/code-security/secure-coding/about-code-scanning" rel="noopener noreferrer"&gt;https://docs.github.com/en/code-security/secure-coding/about-code-scanning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/code-security/secure-coding/configuring-code-scanning" rel="noopener noreferrer"&gt;https://docs.github.com/en/code-security/secure-coding/configuring-code-scanning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.azure.com" rel="noopener noreferrer"&gt;https://www.azure.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/" rel="noopener noreferrer"&gt;https://dotnet.microsoft.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.github.com" rel="noopener noreferrer"&gt;https://www.github.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com" rel="noopener noreferrer"&gt;https://www.docker.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com" rel="noopener noreferrer"&gt;https://www.pulumi.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.codeclimate.com/quality" rel="noopener noreferrer"&gt;https://www.codeclimate.com/quality&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>docker</category>
      <category>csharp</category>
      <category>github</category>
    </item>
    <item>
      <title>API's From Dev to Production - Part 9 - SCA</title>
      <dc:creator>Pete King</dc:creator>
      <pubDate>Fri, 07 May 2021 10:59:17 +0000</pubDate>
      <link>https://dev.to/peteking/api-s-from-dev-to-production-part-9-43fd</link>
      <guid>https://dev.to/peteking/api-s-from-dev-to-production-part-9-43fd</guid>
      <description>&lt;h2&gt;
  
  
  Series Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to &lt;strong&gt;Part 9&lt;/strong&gt; of this blog series that will go from the most basic example of a .net 5 webapi in C#, and the journey from development to production with a &lt;a href="https://dev.to/peteking/shift-left-engineering-4fpp"&gt;&lt;strong&gt;shift-left&lt;/strong&gt;&lt;/a&gt; mindset. We will use &lt;strong&gt;Azure&lt;/strong&gt;, &lt;strong&gt;Docker&lt;/strong&gt;, &lt;strong&gt;GitHub&lt;/strong&gt;, &lt;strong&gt;GitHub Actions&lt;/strong&gt; for CI/C-Deployment and Infrastructure as Code using &lt;strong&gt;Pulumi&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this post we will be looking at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;S&lt;/strong&gt;tatic &lt;strong&gt;C&lt;/strong&gt;ode &lt;strong&gt;A&lt;/strong&gt;nalysis (SCA)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;S&lt;/strong&gt;tatic &lt;strong&gt;C&lt;/strong&gt;ode &lt;strong&gt;A&lt;/strong&gt;nalysis is an important step in the journey from dev to production; and continuing into other cycles such as feature development and more. With an abundance of tools out there on the market, there's bound to be one that suits you. CodeCov.io proved useful, but only for code coverage, by taking the next step and using SCA as well as code coverage, your API products will reap the benefits.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Quality should never take a back seat when building mission critical systems.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/peteking"&gt;
        peteking
      &lt;/a&gt; / &lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-9"&gt;
        Samples.WeatherForecast-Part-9
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This repository is part of the blog post series, API's from Dev to Production - Part 9 on dev.to. Based on the standard .net standard Weather API sample.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;We will be picking-up where we left off in &lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-8-4igl"&gt;Part 8&lt;/a&gt;, which means you’ll need the end-result from &lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-8"&gt;GitHub Repo - Part 8&lt;/a&gt; to start with.&lt;/p&gt;

&lt;p&gt;If you have followed this series all the way through, and I would encourage you to do so, but it isn't necessary if previous posts are knowledge to you already.&lt;/p&gt;




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

&lt;p&gt;We are looking at &lt;em&gt;Static Code Analysis&lt;/em&gt;, another &lt;strong&gt;must have&lt;/strong&gt; when we are going from development to production. These days there are a great number of tools out there from self-hosted to SaaS, for me, I'm more comfortable with less to manage and have opted for a SaaS solution; this certainly doesn't mean you have to. I highly recommend you checkout what is on the market and what will suite your needs.&lt;/p&gt;

&lt;p&gt;We will be waving goodbye to CodeCov, there was a nasty breach and you can read more about that &lt;a href="https://www.zdnet.com/article/codecov-breach-impacted-hundreds-of-customer-networks/"&gt;here&lt;/a&gt; and &lt;a href="https://venturebeat.com/2021/04/26/hashicorp-revoked-private-key-exposed-in-codecov-security-breach/"&gt;here&lt;/a&gt;. However, the move away is not solely because of that reason, the main reason is the SaaS solution we are going to integrate with provides SCA as well as code coverage :)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We will be integrating with &lt;a href="https://www.codeclimate.com/quality"&gt;Code Climate Quality&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Let's get started!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1
&lt;/h2&gt;

&lt;p&gt;Head to their website → Navigate to their Quality product and click → Get Started. &lt;em&gt;This will register Code Climate Quality as a GitHub App&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Once you have complete that journey, you'll be presented with the below - A list of all you public repositories.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhyxb8q693x83yavutib1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhyxb8q693x83yavutib1.png" alt="blog-9-01" width="800" height="754"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2
&lt;/h2&gt;

&lt;p&gt;Add the repo of your choice, in our case, this will be, &lt;code&gt;Samples.WeatherForecast-Part-9&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As soon as you select your chosen repository, Code Climate Quality will starting &lt;em&gt;doing its thing&lt;/em&gt;, just wait, have a cuppa tea :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F88c4erm7o86m4iloj9t1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F88c4erm7o86m4iloj9t1.png" alt="blog-9-02" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Once Code Climate Quality has completed its build and scan etc. you'll be informed about it with a nice little pop-up :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn24pv2tkey95ox8s51ba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn24pv2tkey95ox8s51ba.png" alt="blog-9-03" width="800" height="631"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3
&lt;/h2&gt;

&lt;p&gt;Navigate to your repository dashboard.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Checkout the stats - They look pretty good so far!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You'll notice the &lt;em&gt;maintainability rating&lt;/em&gt; is currently an &lt;strong&gt;A&lt;/strong&gt;. However, &lt;strong&gt;there is no code coverage!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30wkncmxjmvqwedbr0ng.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30wkncmxjmvqwedbr0ng.png" alt="blog-9-04" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4
&lt;/h2&gt;

&lt;p&gt;Let's fix the code coverage.&lt;/p&gt;

&lt;p&gt;Now, Code Climate Quality states in their documentation or technically speaking, doesn't state that C# is supported... Sad face :(&lt;/p&gt;

&lt;p&gt;However, let's not give up so quickly, looking at the &lt;a href="https://docs.codeclimate.com/docs/configuring-test-coverage#supported-languages-and-formats"&gt;supported code coverage formats&lt;/a&gt;, we should be...covered :D&lt;/p&gt;

&lt;p&gt;As we have learnt even in this blog series, &lt;code&gt;lcov&lt;/code&gt; is what we are currently using, and we can also generate &lt;code&gt;Cobertura&lt;/code&gt; as well; these are supported formats.&lt;/p&gt;

&lt;p&gt;We need to feed Code Climate Quality the &lt;code&gt;lcov&lt;/code&gt; file, just like we did with our departed CodeCov (&lt;em&gt;don't forget to remove CodeCov from your GitHub Workflows&lt;/em&gt;).&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 4.1
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Navigate&lt;/em&gt; to Code Climate Quality → Your repo → Repo Settings → Test Coverage.&lt;/p&gt;

&lt;p&gt;You'll see your &lt;code&gt;TEST REPORTER ID&lt;/code&gt;, we need to put this into our GitHub Secrets area.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0a6u1v8h03p20k6zrmz7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0a6u1v8h03p20k6zrmz7.png" alt="blog-9-19" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You should know how to do this by now, put it into your secrets for your repo and call it, &lt;code&gt;CC_TEST_REPORTER_ID&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Step 4.2
&lt;/h3&gt;

&lt;p&gt;Let's add a new step to our GitHub Action CI Workflow.&lt;/p&gt;

&lt;p&gt;Either remove CodeCov.io or before/after the job step, place the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Code coverage [Code Climate]&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;paambaati/codeclimate-action@v2.7.5&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="na"&gt;CC_TEST_REPORTER_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{secrets.CC_TEST_REPORTER_ID}}&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;coverageLocations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workspace }}/path/to/artifacts/testresults/coverage.info:lcov&lt;/span&gt;
    &lt;span class="na"&gt;prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/code/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;We have the environment set to our test reporter id so it know which repo in Code Climate Quality to link it to.&lt;/p&gt;

&lt;p&gt;We've specified the coverage file, please note that there is a, &lt;code&gt;:lcov&lt;/code&gt; at the end to state that it is indeed an &lt;code&gt;lcov&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;The prefix parameter is purely required because of how we have built with Docker.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Prefix can refer to &lt;a href="https://app.codility.com/tests/184820/details/?assignedTo=all&amp;amp;invitedBy=all&amp;amp;review=all&amp;amp;score=any&amp;amp;search=&amp;amp;sort=createdDate&amp;amp;sortOrder=desc&amp;amp;status=active"&gt;sub-commands&lt;/a&gt; of the test reporter.&lt;/p&gt;

&lt;p&gt;We need to use this simply because when we compiled our code inside our Dockerfile, we copied to a local directory inside Docker, we, or more like I, chose to copy to, &lt;code&gt;/code&lt;/code&gt; by setting the working directory like so, &lt;code&gt;WORKDIR /code&lt;/code&gt;, and then copying the code and building. This means all the paths inside the &lt;code&gt;lcov&lt;/code&gt; file will point to &lt;code&gt;/code&lt;/code&gt;. However, inside the GitHub repository, that directory does &lt;strong&gt;not&lt;/strong&gt; exist.&lt;/p&gt;

&lt;p&gt;Very luckily (and with some trial and error on my part), &lt;code&gt;prefix: /code/&lt;/code&gt; resolves this issue :)&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5
&lt;/h2&gt;

&lt;p&gt;Make any commit, or simply trigger the CI workflow.&lt;/p&gt;

&lt;p&gt;Head on over to Code Climate Quality and you should have code coverage stats in your dashboard like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fddxkc92ca9emps57l5hv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fddxkc92ca9emps57l5hv.png" alt="blog-9-05" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6
&lt;/h2&gt;

&lt;p&gt;Code Climate Quality has a GitHub Bot.&lt;/p&gt;

&lt;p&gt;Let's set this up...&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Click&lt;/em&gt; → &lt;strong&gt;Set up&lt;/strong&gt; (&lt;em&gt;Pull request comments&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1d8sya2ix3gw9nwebuar.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1d8sya2ix3gw9nwebuar.png" alt="blog-9-06" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Install the GitHub App...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk19drr11sfth6393vpwi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk19drr11sfth6393vpwi.png" alt="blog-9-07" width="800" height="852"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Confirm the permissions you've set...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgryf4jv9e277u8ijkcue.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgryf4jv9e277u8ijkcue.png" alt="blog-9-08" width="800" height="727"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Select&lt;/em&gt; → &lt;strong&gt;Post additional inline comments on new issues&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fguorngmaz3ktg07cykcp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fguorngmaz3ktg07cykcp.png" alt="blog-9-09" width="800" height="563"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Toggle On&lt;/em&gt; → &lt;strong&gt;Inline issue comments&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb7w4ijuoy51sqt4zc817.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb7w4ijuoy51sqt4zc817.png" alt="blog-9-10" width="800" height="524"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7
&lt;/h2&gt;

&lt;p&gt;Let's test out Code Climate Quality by making some obvious issues and see how it handles it, plus we can test out the developer experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7.1
&lt;/h3&gt;

&lt;p&gt;Modify the, &lt;code&gt;WeatherForecastController.cs&lt;/code&gt;&lt;br&gt;
&lt;em&gt;Change&lt;/em&gt; → &lt;strong&gt;&lt;code&gt;Get()&lt;/code&gt;&lt;/strong&gt; method...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WeatherForecast&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;temps1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Enumerable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;WeatherForecast&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;TemperatureC&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(-&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;55&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Summary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Summaries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Summaries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;temps2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Enumerable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;WeatherForecast&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;TemperatureC&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(-&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;45&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Summary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Summaries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Summaries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Enumerable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temps1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temps2&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;blockquote&gt;
&lt;p&gt;All we've done here is duplicate the amount of responses, but it's slightly different. Take note of the &lt;code&gt;TemperatureC&lt;/code&gt; value in each. Hopefully this will be caught by Code Climate Quality as a &lt;strong&gt;duplication&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Step 7.2
&lt;/h3&gt;

&lt;p&gt;For the next modification, let's change the &lt;code&gt;WeatherForecast.cs&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Add&lt;/em&gt; → New property called, &lt;code&gt;TemperatureFahrenheit&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;TemperatureFahrenheit&lt;/span&gt;  &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;32&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;TemperatureC&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;0.5556&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This is identical, except for the property name, hopefully Code Climate Quality will pick this up as a duplication also.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 8
&lt;/h2&gt;

&lt;p&gt;Raise a PR for all of your changes and with your new CI workflow you should see some &lt;em&gt;action&lt;/em&gt; taken by Code Climate Quality...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgbwft6r33espryg0vcuo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgbwft6r33espryg0vcuo.png" alt="blog-9-11" width="800" height="746"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As you can see from the image above, the Code Climate Quality GitHub Bot has made inline comments as it found a duplication, whoohoo!&lt;/p&gt;

&lt;p&gt;However, don't jump for joy just yet, as this is the only duplication found, it seems it has &lt;strong&gt;not&lt;/strong&gt; picked-up on the property body duplication :(&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 9
&lt;/h2&gt;

&lt;p&gt;Let's enable GitHub Status Checks.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Navigate&lt;/em&gt; to Code Climate Quality → Your repo → Repo Settings → GitHub → Pull request status updates → &lt;em&gt;Click&lt;/em&gt; &lt;strong&gt;Edit&lt;/strong&gt; → &lt;em&gt;Toggle On&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F70hs2odliylvj0axrqxt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F70hs2odliylvj0axrqxt.png" alt="blog-9-12" width="800" height="696"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Back in GitHub, head over to your repository → Branches → Branch Rule → Check the below items so GitHub knows what required:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9rd7y0aj4o2d50gqgjo6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9rd7y0aj4o2d50gqgjo6.png" alt="blog-9-13" width="800" height="104"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;A handy thing in Code Climate Quality is you can see issues within its product itself, not just GitHub, so you have 2 views to look at.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffpj3ietonjz35j5sahjn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffpj3ietonjz35j5sahjn.png" alt="blog-9-14" width="800" height="770"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;By &lt;em&gt;resubmitting&lt;/em&gt; the PR, you will see the GitHub Status Checks shine - &lt;em&gt;Note I still have CodeCov.io in there, but it will be gone in subsequent posts&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl13gekh6v0yn3bfvsdt6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl13gekh6v0yn3bfvsdt6.png" alt="blog-9-15" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Given the status checks are true/false - Code Climate Quality has flagged the duplication issues and now I need to approve this &lt;em&gt;impact&lt;/em&gt; to the codebase. For this example, I will approve so we can see what happens.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a key moment to take note of, by using this workflow we can easily assess the impact and approve or deny, keeping track of codebase health has now never been more simple :)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Famf4uvohvbnuzh6p4err.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Famf4uvohvbnuzh6p4err.png" alt="blog-9-16" width="800" height="808"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;As we expected, we have made things worse! We have duplicated code, our maintainability has gone down, therefore, our rating has dropped from &lt;strong&gt;A&lt;/strong&gt; to &lt;strong&gt;B&lt;/strong&gt;; naturally our test coverage has dropped down as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnn2r254mtxttca2p50qn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnn2r254mtxttca2p50qn.png" alt="blog-9-17" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Technical Debt View
&lt;/h2&gt;

&lt;p&gt;A nice view in Code Climate Quality is the Technical Debt graph, it needs to be taken with a &lt;em&gt;pinch of salt&lt;/em&gt; in terms &lt;strong&gt;resolution time&lt;/strong&gt; and all, however, I do see how this can be useful from time to time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14h69ho0ul6421d2aj8d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14h69ho0ul6421d2aj8d.png" alt="blog-9-18" width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What have we learned?
&lt;/h2&gt;

&lt;p&gt;We have learned about why &lt;strong&gt;Static Code Analysis&lt;/strong&gt; is important and how an optimised workflow using SaaS tools such as Code Climate Quality can impact an engineering teams' daily lives. This is part of the journey to production, releasing without knowing elements such as maintainability and code coverage can so easily create issues down the line, especially with missions critical systems.&lt;/p&gt;

&lt;p&gt;Understanding maintainability and test coverage are vitally important to the quality of the products engineering teams' produce.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CodeCov.io has now been removed.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Next up
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-10-9j1"&gt;Part 10&lt;/a&gt; in this series will be about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SAST - &lt;strong&gt;S&lt;/strong&gt;tatic &lt;strong&gt;A&lt;/strong&gt;pplication &lt;strong&gt;S&lt;/strong&gt;ecurity testing is used to secure software by reviewing the source code of the software to identify sources of vulnerabilities.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  More information
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.azure.com"&gt;https://www.azure.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/"&gt;https://dotnet.microsoft.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.github.com"&gt;https://www.github.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com"&gt;https://www.docker.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com"&gt;https://www.pulumi.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/coverlet-coverage/"&gt;https://github.com/coverlet-coverage/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.codeclimate.com/quality/"&gt;https://www.codeclimate.com/quality/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>docker</category>
      <category>csharp</category>
      <category>github</category>
    </item>
    <item>
      <title>API's From Dev to Production - Part 8 - Status Checks</title>
      <dc:creator>Pete King</dc:creator>
      <pubDate>Thu, 01 Apr 2021 14:58:15 +0000</pubDate>
      <link>https://dev.to/peteking/api-s-from-dev-to-production-part-8-4igl</link>
      <guid>https://dev.to/peteking/api-s-from-dev-to-production-part-8-4igl</guid>
      <description>&lt;h2&gt;
  
  
  Series Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to &lt;strong&gt;Part 8&lt;/strong&gt; of this blog series that will go from the most basic example of a .net 5 webapi in C#, and the journey from development to production with a &lt;a href="https://dev.to/peteking/shift-left-engineering-4fpp"&gt;&lt;strong&gt;shift-left&lt;/strong&gt;&lt;/a&gt; mindset. We will use &lt;strong&gt;Azure&lt;/strong&gt;, &lt;strong&gt;Docker&lt;/strong&gt;, &lt;strong&gt;GitHub&lt;/strong&gt;, &lt;strong&gt;GitHub Actions&lt;/strong&gt; for CI/C-Deployment and Infrastructure as Code using &lt;strong&gt;Pulumi&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this post we will be looking at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More code coverage

&lt;ul&gt;
&lt;li&gt;GitHub Status checks&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;We take advantage of GitHub Status Checks, ensuring our codebase doesn't decrease against our predefined code coverage target; blocking PR's (pull-requests) from being merged. We set a target of 80% knowing our current code coverage is only 36%, we take decisive action to exclude classes from coverage where in this case we believe it is safe. We integrate it all together step by step and achieve our desired outcome of a new CI workflow on PR's with this code coverage gate, and boost code coverage to 95%. &lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/peteking"&gt;
        peteking
      &lt;/a&gt; / &lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-8"&gt;
        Samples.WeatherForecast-Part-8
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This repository is part of the blog post series, API's from Dev to Production - Part 8 on dev.to. Based on the standard .net standard Weather API sample.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;





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

&lt;p&gt;Code coverage is an important metric, out of context it can be a disaster metric, if you are collecting code coverage for a repository, ensuring you have an agreed target in the engineering team and protecting your codebase is of upmost importance.&lt;/p&gt;

&lt;p&gt;How do we ensure there is a target, how to ensure our codebase doesn't get worse with every PR (pull-request)?&lt;/p&gt;

&lt;p&gt;We can take advantage of GitHub Status Checks, with Codecov and other tools, there is tight integration with status checks.&lt;/p&gt;

&lt;p&gt;In this post, let's explore how we can achieve our desired outcome.&lt;/p&gt;




&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;We will be picking-up where we left off in &lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-7-4m71"&gt;Part 7&lt;/a&gt;, which means you’ll need the end-result from &lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-7"&gt;GitHub Repo - Part 7&lt;/a&gt; to start with.&lt;/p&gt;

&lt;p&gt;If you have followed this series all the way through, and I would encourage you to do so, but it isn't necessary if previous posts are knowledge to you already.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't forget to ensure you have setup &lt;a href="https://www.codecov.io"&gt;Codecov.io&lt;/a&gt; with your repository, including the Codecov GitHub Bot - For details on how to set it up, please see &lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-7-4m71"&gt;Part 7&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CI Workflow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create a new branch, call it say, &lt;code&gt;codecov&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add a new yaml file called, &lt;code&gt;ci-pull-request.yml&lt;/code&gt; under your &lt;code&gt;.github\workflows&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;Add the following code to your new workflow:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI Pull Request&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image-name-unit-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unit-tests:latest&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ci&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repo&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# Disabling shallow clone is recommended for improving relevancy of reporting&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unit tests [build]&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker build --target unit-test -t ${{ env.image-name-unit-tests }} .&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unit tests [run]&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker run --rm -v ${{ github.workspace }}/path/to/artifacts/testresults:/code/test/Samples.WeatherForecast.Api.UnitTest/TestResults ${{ env.image-name-unit-tests }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Code coverage [codecov]&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;codecov/codecov-action@v1.2.1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workspace }}/path/to/artifacts/testresults/coverage.info&lt;/span&gt;
          &lt;span class="na"&gt;verbose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You'll notice this is pretty similar to our other main workflow, albeit smaller - We are simply building the unit test image and running it and sending the coverage to Codecov&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Code coverage configuration
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Add the following to your codecov.yml file:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;80%&lt;/span&gt;    &lt;span class="c1"&gt;# the required coverage value&lt;/span&gt;
        &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1%&lt;/span&gt;  &lt;span class="c1"&gt;# the leniency in hitting the target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Create pull request
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create a PR (pull request)&lt;/li&gt;
&lt;li&gt;Using GitHub / GitHub Desktop / Git cmdline, push your PR&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Once the PR is opened, it will kick-off our new workflow, and it will wait for Codecov to come back.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gmemoahge0kr2lpma1q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gmemoahge0kr2lpma1q.png" alt="Alt Text" width="800" height="1003"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Our open pull request
&lt;/h2&gt;

&lt;p&gt;Once Codecov comes back, the status is updated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7nmr2qnu3fkke5iut1l4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7nmr2qnu3fkke5iut1l4.png" alt="Alt Text" width="800" height="698"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We have set the minimum target of 80%, and unfortunately we have a low of 36%.&lt;/p&gt;

&lt;p&gt;It will block our pull request.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This can be a great CI workflow to ensure coverage is at your minimum expectations. This however, cannot tell if your unit tests are good unit tests vs bad unit tests though!&lt;/p&gt;




&lt;h2&gt;
  
  
  Why is the coverage so low?
&lt;/h2&gt;

&lt;p&gt;As you saw in the previous post, we have no &lt;em&gt;unit&lt;/em&gt; tests covering &lt;code&gt;Program.cs&lt;/code&gt; and &lt;code&gt;Startup.cs&lt;/code&gt;. This skews our actual coverage, and for us I'm happy to not cover these with &lt;em&gt;unit&lt;/em&gt; test, so I'd rather exclude those for now.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We could however, cover them with &lt;em&gt;integration&lt;/em&gt; tests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is an attribute in .net to denote that a class etc. is excluded from coverage. This is called, &lt;code&gt;ExcludeFromCodeCoverageAttribute&lt;/code&gt;. For more information, please visit &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.excludefromcodecoverageattribute?view=net-5.0"&gt;ExcludeFromCodeCoverageAttribute Class&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Place this attribute on both, &lt;code&gt;Program.cs&lt;/code&gt; and &lt;code&gt;Startup.cs&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Diagnostics.CodeAnalysis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Samples.WeatherForecast.Api&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ExcludeFromCodeCoverage&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&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 csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Diagnostics.CodeAnalysis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Samples.WeatherForecast.Api&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ExcludeFromCodeCoverage&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Startup&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Test locally
&lt;/h2&gt;

&lt;p&gt;Kick-off your local unit-tests by executing &lt;code&gt;.\unit-test.ps1&lt;/code&gt;, you should see a big improvement in coverage as we have decisively and specifically excluded items.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65awfdy4b8p63nyhvn8d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65awfdy4b8p63nyhvn8d.png" alt="Alt Text" width="784" height="402"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39p2x6gd81kfbaujuvq1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39p2x6gd81kfbaujuvq1.png" alt="Alt Text" width="800" height="656"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We can see that the is now a difference between what was covered previously and what is covered now.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncgje2dgql8egnlghtip.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncgje2dgql8egnlghtip.png" alt="Alt Text" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Given we are above the target of 80%, the GitHub Status Check will allow it to pass.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp9knnwwdixd1jp1uaqt2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp9knnwwdixd1jp1uaqt2.png" alt="Alt Text" width="800" height="741"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Our code coverage goes up as indicated, we can also see this directly in Codecov.io; a nice graph, and who doesn't love a nice graph!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwbc8pq3ugp3btio6du5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwbc8pq3ugp3btio6du5.png" alt="Alt Text" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Our files are excluded from coverage, hence the code coverage boost as we expected.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsbd0kb8sxdzwkbcteto8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsbd0kb8sxdzwkbcteto8.png" alt="Alt Text" width="800" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Our badge proudly displays our code coverage percentage.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Go one step further
&lt;/h2&gt;

&lt;p&gt;As you can see from our changes, we've created a new workflow and we have GitHub Status Checks to ensure we have quality gates with our code based on a code coverage target. We can go one step further with this PR workflow by including most of our other main workflow...&lt;/p&gt;

&lt;p&gt;We can add the &lt;code&gt;App [build]&lt;/code&gt;, &lt;code&gt;App [scan]&lt;/code&gt; steps, one thing we should not do though, is publish to our container registry, we certainly don't want to do that; as we are only at the PR stage!&lt;/p&gt;

&lt;p&gt;The benefit of doing this is running the container scan on a final build image, and we will break our build if the scan fails based on our configuration for it. We can also introduce another GitHub Status Check to ensure the &lt;code&gt;ci&lt;/code&gt; workflow completes successfully.&lt;/p&gt;

&lt;p&gt;Let's give this a go...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/peteking/samples-weatherforecast-part-8:${{ github.sha }}&lt;/span&gt;
&lt;span class="nn"&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 yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;...&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;App [build]&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker build -t ${{ env.image-name }} .&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;App [scan]&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/container-scan@v0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;image-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.image-name }}&lt;/span&gt;
          &lt;span class="na"&gt;severity-threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MEDIUM&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.repository_owner }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GH_CR }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Next step
&lt;/h3&gt;

&lt;p&gt;Repository Settings → Branch Rule → Check for &lt;code&gt;ci&lt;/code&gt; to mark it as &lt;strong&gt;Required&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo308z1eiinsphnbxpmv6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo308z1eiinsphnbxpmv6.png" alt="Alt Text" width="800" height="624"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Final step
&lt;/h3&gt;

&lt;p&gt;Commit → Push → Create Pull-Request&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You should now see your CI workflow run and go through the status checks. If the CI workflow fails, for example, the App [scan] step finds a CVE and it is deemed we do not pass, this will stop engineers from merging in code that will end include an open CVE.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8b8m8qvfk46o0u69n68.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8b8m8qvfk46o0u69n68.png" alt="Alt Text" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All status checks pass!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  We have some duplication
&lt;/h2&gt;

&lt;p&gt;Yes, we now have some duplicated yaml code in our CI workflow and our build and push workflow. We can extract and share this out potentially with composite GitHub Action Workflows. However, I have not covered this in this post. This may be covered in another post or it may be tackled down the line in this blog series.&lt;/p&gt;




&lt;h2&gt;
  
  
  What have we learned?
&lt;/h2&gt;

&lt;p&gt;We have learned GitHub Status Checks are powerful tools and how to take advantage of them. We have configured a new CI workflow that acts as a quality gate for code coverage with a minimum of 80% to pass; we can be somewhat happy that our codebase won't degrade its code coverage over time with each PR.&lt;/p&gt;

&lt;p&gt;We extended our CI workflow with our build and scan steps to protect ourselves from potential CVE's before merging; and finally building and pushing our image.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If we see an open CVE when doing our PR, we can investigate, rectify if needed and re-submit the PR. Long-live &lt;strong&gt;&lt;em&gt;shift-left!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Next up
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-9-43fd"&gt;Part 9&lt;/a&gt; in this series will be about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static code analysis (SCA)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  More information
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.azure.com"&gt;https://www.azure.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/"&gt;https://dotnet.microsoft.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.github.com"&gt;https://www.github.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com"&gt;https://www.docker.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com"&gt;https://www.pulumi.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/coverlet-coverage/"&gt;https://github.com/coverlet-coverage/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.codecov.io"&gt;https://www.codecov.io&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>docker</category>
      <category>csharp</category>
      <category>github</category>
    </item>
    <item>
      <title>API's From Dev to Production - Part 7 - Code Coverage</title>
      <dc:creator>Pete King</dc:creator>
      <pubDate>Thu, 25 Mar 2021 15:23:02 +0000</pubDate>
      <link>https://dev.to/peteking/api-s-from-dev-to-production-part-7-4m71</link>
      <guid>https://dev.to/peteking/api-s-from-dev-to-production-part-7-4m71</guid>
      <description>&lt;h2&gt;
  
  
  Series Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to &lt;strong&gt;Part 7&lt;/strong&gt; of this blog series that will go from the most basic example of a .net 5 webapi in C#, and the journey from development to production with a &lt;a href="https://dev.to/peteking/shift-left-engineering-4fpp"&gt;&lt;strong&gt;shift-left&lt;/strong&gt;&lt;/a&gt; mindset. We will use &lt;strong&gt;Azure&lt;/strong&gt;, &lt;strong&gt;Docker&lt;/strong&gt;, &lt;strong&gt;GitHub&lt;/strong&gt;, &lt;strong&gt;GitHub Actions&lt;/strong&gt; for CI/C-Deployment and Infrastructure as Code using &lt;strong&gt;Pulumi&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this post we will be looking at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code coverage&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;We understood code coverage is an important metric, but a metric that shouldn't looked at in isolation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Achieving a high percentage of code coverage is a great goal to shoot for, but it should be paired with having a robust, quality, high-value test suite.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We utilise &lt;a href="https://github.com/coverlet-coverage/coverlet" rel="noopener noreferrer"&gt;Coverlet&lt;/a&gt; to output code coverage files (lcov format), setup and configure Codecov, update our Dockerfile and GitHub Actions Workflow for code coverage, and finally, a little insight into the Codecov settings file to set a target.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.codecov.io" rel="noopener noreferrer"&gt;Codecov.io&lt;/a&gt; &lt;del&gt;is&lt;/del&gt; was a great solution and easy to integrate, I can highly recommend the product; I set it up in less than 10 minutes!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/peteking" rel="noopener noreferrer"&gt;
        peteking
      &lt;/a&gt; / &lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-7" rel="noopener noreferrer"&gt;
        Samples.WeatherForecast-Part-7
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This repository is part of the blog post series, API's from Dev to Production - Part 7 on dev.to. Based on the standard .net standard Weather API sample.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;





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

&lt;p&gt;We would all like to write perfect, bug free code, but we know in reality this will not happen, we are human after all. So, we write tests whereby we arrange, act and assert a scenario, in turn, this uses our code that will effectively be running in production, servicing the needs of our customers. A good metric to track is code coverage, it can help assess the quality of our suite of tests; how much of our tests actually execute lines of code (and more) that will be servicing those needs of our customers?&lt;/p&gt;

&lt;p&gt;Code coverage is a measurement used to express which lines of code were executed by tests. Typically there is the use of three primary terms to describe each line executed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hit indicates that the source code was executed by a test.&lt;/li&gt;
&lt;li&gt;Partial indicates that the source code was not fully executed by the a test; there are remaining branches that were not executed.&lt;/li&gt;
&lt;li&gt;Miss indicates that the source code was not executed by tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are many products that offer solutions, here we are going to look into one of them, it's free for open source, and it has a paid plans of course too.&lt;/p&gt;

&lt;h3&gt;
  
  
  What percentage of coverage should I aim for?
&lt;/h3&gt;

&lt;p&gt;I'm afraid I have some bad news... There is no magic formula or silver bullet. A very high percentage of coverage could still be problematic if certain critical parts of the application are not covered, or if the tests are not robust enough to capture failure when they execute; either locally or as part of CI.&lt;/p&gt;

&lt;p&gt;Having said that, some general views are it's about 80%, however, you must be aware that it is a target and it is not something to achieve immediately and to cut corners in order to hit the target. Some product teams may feel under pressure to meet targets and create broad tests to meet those targets. Don't fall into this trap, you don't want to have laser focus trying to hit every line of code, instead, the focus should be on the business requirements of your application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code coverage reports
&lt;/h3&gt;

&lt;p&gt;As you'll see in this post, code coverage reports provide a way to identify critical misses in your testing. You API/application will have many tests, and can at times, be hard to navigate, all you'll know is some of your tests are failing, and here is the list. Code coverage reports provide a way to dig into the details and find actions to take; find out what is &lt;em&gt;not&lt;/em&gt; tested.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code coverage does not equal good tests
&lt;/h3&gt;

&lt;p&gt;Remember, just because code is covered with tests, it does not mean everything is all good. If your tests are not the right tests, i.e. the test does not test the right elements in the right way, it means your tests are of low quality and value. However, your code coverage is high... so what? You see, code coverage is a metric that must be viewed in context with other metrics and other practices.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Achieving a high percentage of code coverage is a great goal to shoot for, but it should be paired with having a robust, quality, high-value test suite.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;We will be picking-up where we left off in &lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-6-1p34"&gt;Part 6&lt;/a&gt;, which means you’ll need the end-result from &lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-6" rel="noopener noreferrer"&gt;GitHub Repo - Part 6&lt;/a&gt; to start with.&lt;/p&gt;

&lt;p&gt;If you have followed this series all the way through, and I would encourage you to do so, but it isn't necessary if previous posts are knowledge to you already.&lt;/p&gt;




&lt;h2&gt;
  
  
  Coverlet
&lt;/h2&gt;

&lt;p&gt;Coverlet is the number one way (in my view) to add cross platform code coverage to .NET, with support for line, branch and method coverage. It works with .NET Framework on Windows and .NET Core on all supported platforms. In short, it's great!&lt;/p&gt;

&lt;p&gt;Let's set it up in our project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1
&lt;/h3&gt;

&lt;p&gt;To add Coverlet to our project, we need to add the, &lt;code&gt;coverlet.msbuild&lt;/code&gt; package to our unit test project.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Open&lt;/em&gt; your terminal, &lt;em&gt;cd&lt;/em&gt; to the folder where your unit test project is located, and run the following 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;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;coverlet.msbuild&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you're using Visual Studio you can simply add a new reference using the UI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This command will use Nuget and update your &lt;code&gt;.csproj&lt;/code&gt; file like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"coverlet.msbuild"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"3.0.2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;IncludeAssets&amp;gt;&lt;/span&gt;runtime; build; native; contentfiles; analyzers; buildtransitive&lt;span class="nt"&gt;&amp;lt;/IncludeAssets&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;PrivateAssets&amp;gt;&lt;/span&gt;all&lt;span class="nt"&gt;&amp;lt;/PrivateAssets&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/PackageReference&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2
&lt;/h3&gt;

&lt;p&gt;Update our &lt;code&gt;dotnet test&lt;/code&gt; command to collect code coverage metrics.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;-p:CollectCoverage=true \
-p:CoverletOutput="TestResults/coverage.info" \
-p:CoverletOutputFormat=lcov
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Dockerfile - [Unit test runner] section
&lt;/h3&gt;

&lt;p&gt;This is what our modified Unit test runner section looks like now with the additional parameters for code coverage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Unit test runner&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;unit-test&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /code/test/Samples.WeatherForecast.Api.UnitTest&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; dotnet test \&lt;/span&gt;
    -c Release \
    --runtime linux-musl-x64 \
    --no-restore \
    --no-build \
    --logger "trx;LogFileName=test_results_unit_test.trx" \
    -p:CollectCoverage=true \
    -p:CoverletOutput="TestResults/coverage.info" \
    -p:CoverletOutputFormat=lcov
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;We have chosen the &lt;code&gt;lcov&lt;/code&gt; format simple because the tools we are going to use next require this format.&lt;/p&gt;

&lt;p&gt;You can output other formats and even multiple formats at the same time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As a quick example, if you wished to output lcov and opencover formats, you could modify the last two lines like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;-p:CoverletOutput="TestResults/" \
-p:CoverletOutputFormat=\"lcov,opencover\"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 3
&lt;/h3&gt;

&lt;p&gt;We now need to build locally, we have our &lt;code&gt;.\unit-test.ps1&lt;/code&gt; script, run that and we should have output from Coverlet on our code coverage, let's double-check just to make sure.&lt;/p&gt;

&lt;p&gt;Look in your &lt;code&gt;/TestResults&lt;/code&gt; directory, in there you should have an additional file called, &lt;code&gt;coverage.info&lt;/code&gt;.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Codecov
&lt;/h2&gt;

&lt;p&gt;If you are looking for a nice elegant solution, &lt;a href="https://www.codecov.io" rel="noopener noreferrer"&gt;Codecov&lt;/a&gt; has got you covered; no pun intended... or was there... :P&lt;/p&gt;

&lt;p&gt;Let's setup Codecov.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Navigate&lt;/em&gt; to &lt;a href="https://www.codecov.io" rel="noopener noreferrer"&gt;Codecov&lt;/a&gt;, and &lt;em&gt;sign up&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I've used my GitHub account.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once connected you'll see you have no repositories setup like the screenshot below:&lt;/p&gt;

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




&lt;p&gt;&lt;em&gt;Select&lt;/em&gt; → &lt;strong&gt;Add repository&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this example, I'm going to &lt;em&gt;Select&lt;/em&gt; the &lt;br&gt;
&lt;strong&gt;Samples.WeatherForecast-Part-7&lt;/strong&gt; repository.&lt;/p&gt;

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


&lt;h3&gt;
  
  
  New step
&lt;/h3&gt;

&lt;p&gt;We need to add a new step to upload our coverage results to Codecov, and as you've seen previously, this is the power of GitHub Actions - There is a pre-built Action from Codecov to do that for us!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For more information, please see, &lt;a href="https://github.com/marketplace/actions/codecov" rel="noopener noreferrer"&gt;GitHub Action for Codecov&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Immediately after the &lt;code&gt;Unit test [publish]&lt;/code&gt; step, please add a new step with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Code coverage [codecov]&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;codecov/codecov-action@v1.2.1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workspace }}/path/to/artifacts/testresults/coverage.info&lt;/span&gt;
    &lt;span class="na"&gt;verbose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Commit time for Codecov
&lt;/h3&gt;

&lt;p&gt;If you've carried out this exercise locally like myself, and ensured the &lt;code&gt;coverage.info&lt;/code&gt; is generated by running &lt;code&gt;.\unit-test.ps1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's time you committed your changes, your build should kick-off and Codecov should know all about it.&lt;/p&gt;

&lt;p&gt;Once your build job has executed the Codecov action, your results should be available to see in the Codecov platform.&lt;/p&gt;

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




&lt;p&gt;If we dive into the breakdown of the code, you should see something similar below:&lt;/p&gt;

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

&lt;p&gt;Looking at our code coverage, it isn't that fantastic, overall we have achieved &lt;strong&gt;36%&lt;/strong&gt;, but at least we can take some action. We have our sample unit test, which is incredibly basic, and given we have no tests covering &lt;code&gt;program.cs&lt;/code&gt; and &lt;code&gt;startup.cs&lt;/code&gt; it will hurt our overall coverage percentage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code coverage badge
&lt;/h3&gt;

&lt;p&gt;Everyone likes a badge on their 'readme.md', let's add our Codecov bage.&lt;/p&gt;

&lt;p&gt;Add the following code into your, &lt;code&gt;readme.md&lt;/code&gt; file, of course, yours will be slightly different to mine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;![codecov&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://codecov.io/gh/peteking/Samples.WeatherForecast-Part-7/branch/main/graph/badge.svg?token=KZW5MORPPY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;](https://codecov.io/gh/peteking/Samples.WeatherForecast-Part-7)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Where can I get the code you need, well, that's easy...&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Navigate&lt;/em&gt; to your &lt;strong&gt;project&lt;/strong&gt; in &lt;strong&gt;Codecov&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Click&lt;/em&gt; → &lt;strong&gt;Settings&lt;/strong&gt; (top-right)&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;Sidebar&lt;/strong&gt; &lt;em&gt;Click&lt;/em&gt; → &lt;strong&gt;Badges&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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




&lt;h3&gt;
  
  
  Codecov Repository Configuration
&lt;/h3&gt;

&lt;p&gt;First of all, install the Codecov Bot in GitHub using the following link:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/apps/codecov" rel="noopener noreferrer"&gt;GitHub App - Codecov Bot&lt;/a&gt;&lt;/p&gt;

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




&lt;p&gt;You can configure Codecov with various different configurations, for more information, please see, &lt;a href="https://docs.codecov.io/docs/codecov-yaml" rel="noopener noreferrer"&gt;About the Codecov yaml&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we have set the &lt;strong&gt;target&lt;/strong&gt; coverage of &lt;strong&gt;30%&lt;/strong&gt;, this is quite low as we know, but this is just an example to show how it works.&lt;/p&gt;

&lt;p&gt;Commit your code, and wait for your results to show-up, it should all *&lt;em&gt;pass&lt;/em&gt; fine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30%&lt;/span&gt;    &lt;span class="c1"&gt;# the required coverage value&lt;/span&gt;
        &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1%&lt;/span&gt;  &lt;span class="c1"&gt;# the leniency in hitting the target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;We will take advantage of the above settings in the next blog post :)&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What have we learned?
&lt;/h2&gt;

&lt;p&gt;We have learned how to add code coverage, in particular &lt;a href="https://www.codecov.io" rel="noopener noreferrer"&gt;Codecov.io&lt;/a&gt; to our API. In order to achieve these results, we have seen how to add &lt;a href="https://github.com/coverlet-coverage/coverlet" rel="noopener noreferrer"&gt;Coverlet&lt;/a&gt; and output our coverage files (specifically lcov). We've adapted our Dockerfile, GitHub Actions Workflow, configured a code coverage target (albeit a low one) and even added a badge to our GitHub repository readme file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📄 UPDATE - &lt;strong&gt;CodeCov.io&lt;/strong&gt;&lt;br&gt;
We end up removing CodeCov.io from our API in &lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-9-43fd"&gt;Part 9&lt;/a&gt;, however, it's still worthwhile going through this process. Even so, CodeCov.io could be OK for you, don't let me stop you! 👍&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Next up
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-8-4igl"&gt;Part 8&lt;/a&gt; in this series will be about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More code coverage - We will set a realistic code coverage target.

&lt;ul&gt;
&lt;li&gt;GitHub Status checks - How to protect your code?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  More information
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.azure.com" rel="noopener noreferrer"&gt;https://www.azure.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/" rel="noopener noreferrer"&gt;https://dotnet.microsoft.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.github.com" rel="noopener noreferrer"&gt;https://www.github.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com" rel="noopener noreferrer"&gt;https://www.docker.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com" rel="noopener noreferrer"&gt;https://www.pulumi.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/coverlet-coverage/coverlet" rel="noopener noreferrer"&gt;https://github.com/coverlet-coverage/coverlet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.codecov.io" rel="noopener noreferrer"&gt;https://www.codecov.io&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>docker</category>
      <category>csharp</category>
      <category>github</category>
    </item>
    <item>
      <title>API's From Dev to Production - Part 6 - Unit Tests</title>
      <dc:creator>Pete King</dc:creator>
      <pubDate>Fri, 19 Mar 2021 15:02:25 +0000</pubDate>
      <link>https://dev.to/peteking/api-s-from-dev-to-production-part-6-1p34</link>
      <guid>https://dev.to/peteking/api-s-from-dev-to-production-part-6-1p34</guid>
      <description>&lt;h2&gt;
  
  
  Series Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to &lt;strong&gt;Part 6&lt;/strong&gt; of this blog series that will go from the most basic example of a .net 5 webapi in C#, and the journey from development to production with a &lt;a href="https://dev.to/peteking/shift-left-engineering-4fpp"&gt;&lt;strong&gt;shift-left&lt;/strong&gt;&lt;/a&gt; mindset. We will use &lt;strong&gt;Azure&lt;/strong&gt;, &lt;strong&gt;Docker&lt;/strong&gt;, &lt;strong&gt;GitHub&lt;/strong&gt;, &lt;strong&gt;GitHub Actions&lt;/strong&gt; for CI/C-Deployment and Infrastructure as Code using &lt;strong&gt;Pulumi&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this post we will be looking at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unit Testing - with Docker and GitHub Actions&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;We optimised the image layering by ensuring we &lt;code&gt;COPY&lt;/code&gt; our &lt;code&gt;.csproj&lt;/code&gt; files separately in order for the Docker build engine to use its build cache more effectively.&lt;/p&gt;

&lt;p&gt;We add a basic unit test (in principle), use the &lt;a href="https://marketplace.visualstudio.com/items?itemName=formulahendry.dotnet-test-explorer" rel="noopener noreferrer"&gt;.NET Core Test Explorer&lt;/a&gt; for ease of use within VS Code.&lt;/p&gt;

&lt;p&gt;We build 2 images, one for unit testing and our final image, we achieved this by using the &lt;code&gt;--target&lt;/code&gt; option on the &lt;code&gt;docker build&lt;/code&gt; command. This enabled us to run Docker for our unit tests separately (as it needs the SDK to run).&lt;/p&gt;

&lt;p&gt;We add the same capability to our GitHub Actions' Workflow and persist the unit test results as an artifact, and use a GitHub Action to publish a pretty test report against the build job. &lt;a href="https://github.com/dorny/test-reporter" rel="noopener noreferrer"&gt;GitHub Actions - Test Reporter&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/peteking" rel="noopener noreferrer"&gt;
        peteking
      &lt;/a&gt; / &lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-6" rel="noopener noreferrer"&gt;
        Samples.WeatherForecast-Part-6
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This repository is part of the blog post series, API's from Dev to Production - Part 6 on dev.to. Based on the standard .net standard Weather API sample.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;





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

&lt;p&gt;In Part 5 we were able to get our healthcheck in our API and even our Dockerfile. This is a natural progression to move onto other key capabilities we should have in our API engineering flow - Unit tests!&lt;/p&gt;

&lt;p&gt;We won't go so in depth into the good practices around uniting testing per se, nor will I describe the different types of unit testing; socialable and solitary unit testing.&lt;/p&gt;

&lt;p&gt;What we will cover is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to add unit testing to our out-of-the-box weather API that we have?&lt;/li&gt;
&lt;li&gt;How that can work with our Docker setup?&lt;/li&gt;
&lt;li&gt;How we can run unit tests locally?&lt;/li&gt;
&lt;li&gt;How we can run unit tests in GitHub Actions?&lt;/li&gt;
&lt;li&gt;How we can report the unit test results in GitHub Actions?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;We will be picking-up where we left off in &lt;a href=""&gt;Part 5&lt;/a&gt;, which means you’ll need the end-result from &lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-5" rel="noopener noreferrer"&gt;GitHub Repo - Part 5&lt;/a&gt; to start with.&lt;/p&gt;

&lt;p&gt;If you have followed this series all the way through, and I would encourage you to do so, but it isn't necessary if previous posts are knowledge to you already.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Requirements
&lt;/h2&gt;

&lt;p&gt;We will need one further VS Code extension that will prove useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.NET Core Test Explorer - &lt;a href="https://marketplace.visualstudio.com/items?itemName=formulahendry.dotnet-test-explorer" rel="noopener noreferrer"&gt;https://marketplace.visualstudio.com/items?itemName=formulahendry.dotnet-test-explorer&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Add new unit test project
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Open&lt;/em&gt; any terminal such as Windows Terminal.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Navigate&lt;/em&gt; to the &lt;strong&gt;root folder&lt;/strong&gt; of the project.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In my case, I've put it into a GitHub folder under a folder called, 'Samples.WeatherForecast'.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Execute&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new xunit &lt;span class="nt"&gt;-o&lt;/span&gt; ./test/Samples.WeatherForecast.Api.UnitTest
&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/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fulk58sgu5y8no9bs4smm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fulk58sgu5y8no9bs4smm.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Add reference
&lt;/h3&gt;

&lt;p&gt;We now need to add a reference to our API to our unit test project.&lt;br&gt;
Staying in the same terminal session; ensuring you are still in the &lt;strong&gt;root&lt;/strong&gt; directory.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Execute&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add ./test/Samples.WeatherForecast.Api.UnitTest/Samples.WeatherForecast.Api.UnitTest.csproj reference ./src/Samples.WeatherForecast.Api/Samples.WeatherForecast.Api.csproj
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Open VS Code
&lt;/h2&gt;

&lt;p&gt;Let's get started and open VS Code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TIP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In your terminal, &lt;em&gt;execute&lt;/em&gt;: &lt;code&gt;code .&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You should now have your API project as before, but now you should have a &lt;code&gt;test&lt;/code&gt; folder and your new xUnit test project too.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Navigate&lt;/em&gt; to the test project and &lt;em&gt;Click&lt;/em&gt; → &lt;code&gt;UnitTest1.cs&lt;/code&gt;.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Unit test
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Let's rename the file from, &lt;code&gt;UnitTest.cs&lt;/code&gt; to &lt;code&gt;WeatherForecastControllersTests.cs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Change the namespace from, &lt;code&gt;UnitTest1&lt;/code&gt; to &lt;code&gt;Samples.WeatherForecast.Api.UnitTest&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add a new (overwrite existing test) with a new test called, &lt;code&gt;ShouldReturnAListOfValues()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Full code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Xunit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Logging.Abstractions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Samples.WeatherForecast.Api.UnitTest&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherForecastControllerTests&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ShouldReturnAListOfValues&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Arrange&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;NullLogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Samples&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WeatherForecast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Controllers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WeatherForecastController&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Samples&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WeatherForecast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Controllers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WeatherForecastController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Act&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="c1"&gt;// Assert&lt;/span&gt;
            &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NotNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your VS Code screen should look like mine below:&lt;/p&gt;

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




&lt;blockquote&gt;
&lt;p&gt;Now, this is not the best unit test in the world, but the principle is still the same.&lt;/p&gt;

&lt;p&gt;This is to demonstrate how you can set it up and where to put your unit tests.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Navigate&lt;/em&gt; to the &lt;strong&gt;.NET Core Test Explorer&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg3a8arhuipmgfcrgtk51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg3a8arhuipmgfcrgtk51.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;You'll notice that it says, "Test1", this was the original test name in our &lt;code&gt;UnitTest.cs&lt;/code&gt; file, it needs to re-scan to find tests.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Click&lt;/em&gt; &lt;strong&gt;Refresh&lt;/strong&gt;&lt;/p&gt;

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



&lt;p&gt;You should see our new test method popup like below:&lt;/p&gt;

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



&lt;p&gt;&lt;em&gt;Click&lt;/em&gt; the &lt;strong&gt;Play&lt;/strong&gt; button&lt;/p&gt;

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



&lt;p&gt;You should see a nice green tick in two places, on the test explorer, and in your code against the test method.&lt;/p&gt;

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


&lt;h2&gt;
  
  
  Dockerfile
&lt;/h2&gt;

&lt;p&gt;Now we have the unit test(s) working, we need to modify our Dockerfile, and I'm sorry to say, but we are going to change it quite a lot...&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We need to add in the unit test stage, which will introduce a new &lt;code&gt;ENTRYPOINT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Whilst we are here, we can optimise the build process a little too, and I'll explain why this is important - it goes back to Part 2 where we optimised for size, and we went through how the Docker build cache works. However, we haven't taken complete full advantage of it here... and we should!&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Let's start with the optimisations
&lt;/h2&gt;

&lt;p&gt;We are going to start with the optimisations simply because for our unit tests to run, we will need some of those changes.&lt;/p&gt;

&lt;p&gt;We will go from line 1 to the end of the file just so it's all nice &amp;amp; clear.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Feel free to clear your&lt;/em&gt; &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; VERSION=5.0-alpine&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/runtime-deps:${VERSION}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="c"&gt;# HEALTHCHECK --interval=60s --timeout=3s --retries=3 \&lt;/span&gt;
&lt;span class="c"&gt;#    CMD wget localhost:8080/health -q -O - &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Here we set a variable for the version and we setup our &lt;strong&gt;base&lt;/strong&gt; image, set our working directory to &lt;code&gt;/app&lt;/code&gt;, &lt;code&gt;EXPOSE&lt;/code&gt; port 8080, and we have our commented-out &lt;code&gt;HEALTHCHECK&lt;/code&gt;; this we don't really need, but it's nice to have it on hand if we do.&lt;/p&gt;

&lt;p&gt;This was at the end of our &lt;code&gt;Dockerfile&lt;/code&gt; previously, but I've moved it to the top as it's a common thing to see in multi-stage builds.&lt;/p&gt;
&lt;/blockquote&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:${VERSION}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /code&lt;/span&gt;

&lt;span class="c"&gt;# Copy and restore as distinct layers&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ["src/Samples.WeatherForecast.Api/Samples.WeatherForecast.Api.csproj", "src/Samples.WeatherForecast.Api/Samples.WeatherForecast.Api.csproj"]&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ["test/Samples.WeatherForecast.Api.UnitTest/Samples.WeatherForecast.Api.UnitTest.csproj", "test/Samples.WeatherForecast.Api.UnitTest/"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Here we are using the dotnet SDK and we label that as our &lt;code&gt;build&lt;/code&gt; image.&lt;/p&gt;

&lt;p&gt;We set the &lt;code&gt;WORKDIR&lt;/code&gt; to &lt;code&gt;/code&lt;/code&gt; - I didn't want to call it &lt;code&gt;/app&lt;/code&gt; as I wanted a clear difference between the code to build and the final deploy folder.&lt;/p&gt;

&lt;p&gt;The most important thing to note is that we are now &lt;strong&gt;specifically&lt;/strong&gt; using &lt;code&gt;COPY&lt;/code&gt; to copy our API project, and our Unit Test project&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Why copy each one instead of what we had before, which was using &lt;code&gt;COPY . .&lt;/code&gt; ?&lt;/p&gt;

&lt;p&gt;Well, it's because if any files change, the &lt;em&gt;entire layer&lt;/em&gt; will need to be &lt;em&gt;re-created each time&lt;/em&gt;, if we copy each &lt;em&gt;one at a time&lt;/em&gt;, Docker will create a &lt;em&gt;new layer&lt;/em&gt; for each &lt;code&gt;COPY&lt;/code&gt; command. Therefore, Docker can use its build cache to see if it needs copying or not; Docker will highly optimise the build process.&lt;/p&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore &lt;span class="s2"&gt;"src/Samples.WeatherForecast.Api/Samples.WeatherForecast.Api.csproj"&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; linux-musl-x64
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore &lt;span class="s2"&gt;"test/Samples.WeatherForecast.Api.UnitTest/Samples.WeatherForecast.Api.UnitTest.csproj"&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; linux-musl-x64
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;We then need to run &lt;code&gt;dotnet restore&lt;/code&gt; for each project, similar to what we had before.&lt;/p&gt;

&lt;p&gt;Finally, we execute &lt;code&gt;COPY . .&lt;/code&gt; to copy any other files that could have changed.&lt;/p&gt;
&lt;/blockquote&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet build &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="s2"&gt;"src/Samples.WeatherForecast.Api/Samples.WeatherForecast.Api.csproj"&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--runtime&lt;/span&gt; linux-musl-x64 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--no-restore&lt;/span&gt;    

&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet build &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="s2"&gt;"test/Samples.WeatherForecast.Api.UnitTest/Samples.WeatherForecast.Api.UnitTest.csproj"&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;-r&lt;/span&gt; linux-musl-x64 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--no-restore&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Now we build each project using &lt;code&gt;dotnet build&lt;/code&gt;, ensuring we set, &lt;code&gt;--no-restore&lt;/code&gt; , just like we did before.&lt;/p&gt;
&lt;/blockquote&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Unit test runner&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;unit-test&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /code/test/Samples.WeatherForecast.Api.UnitTest&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; dotnet test \&lt;/span&gt;
    -c Release \
    --runtime linux-musl-x64 \
    --no-restore \
    --no-build \
    --logger "trx;LogFileName=test_results_unit_test.trx"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This is a big change to note here, this is our &lt;strong&gt;&lt;em&gt;unit test runner&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We use  &lt;code&gt;FROM build&lt;/code&gt; as we need to use the .NET SDK, we label this one as, &lt;code&gt;unit-test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Set the &lt;code&gt;WORKDIR&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We create a &lt;strong&gt;new ENTRYPOINT&lt;/strong&gt; and execute &lt;code&gt;dotnet test&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;strong&gt;&lt;em&gt;This is where the magic happens!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We have &lt;em&gt;another&lt;/em&gt; &lt;code&gt;ENTRYPOINT&lt;/code&gt; - How can that work I hear you ask?&lt;/p&gt;

&lt;p&gt;Well, it comes down to how Docker processes the &lt;code&gt;Dockerfile&lt;/code&gt; - The last &lt;code&gt;ENTRYPOINT&lt;/code&gt; command is the &lt;em&gt;one it will run&lt;/em&gt;; kind of like a default if you will. &lt;/p&gt;

&lt;p&gt;So now I hear you ask, how can we make use of 2 &lt;code&gt;ENTRYPOINT&lt;/code&gt; commands then...?&lt;/p&gt;

&lt;p&gt;Part of the &lt;code&gt;docker build&lt;/code&gt; command is an option called, &lt;code&gt;--target&lt;/code&gt;. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For more information about the &lt;code&gt;docker build&lt;/code&gt; command, please see, &lt;a href="https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target" rel="noopener noreferrer"&gt;Docker Docs - docker build - target&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;--target&lt;/code&gt; will target a specific &lt;strong&gt;build stage&lt;/strong&gt;, the label we need to use is defined using the &lt;code&gt;FROM&lt;/code&gt; statement, it's the &lt;code&gt;AS&lt;/code&gt; we need - in our case here, we have &lt;code&gt;FROM build AS unit-test&lt;/code&gt;, therefore, the build stage name is, &lt;strong&gt;unit-test&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When we wish to execute our unit test runner using Docker, we can simply build our &lt;code&gt;Dockerfile&lt;/code&gt; up to this build stage; the remaining instructions in the &lt;code&gt;Dockerfile&lt;/code&gt; are ignored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build [unit-test]
&lt;/h2&gt;

&lt;p&gt;To build our docker image, we can execute the following command in your terminal of 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;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unit-test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;samples-weatherforecast-unit-test:v6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Run [unit-test]
&lt;/h3&gt;

&lt;p&gt;If we wish to execute our unit tests, we can run the following 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;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--rm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;samples-weatherforecast-unit-test:v6&lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will run our instructions in that build stage, which in our case are our unit tests using, &lt;code&gt;dotnet test&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test Results
&lt;/h2&gt;

&lt;p&gt;When you execute &lt;code&gt;docker run&lt;/code&gt; on this &lt;em&gt;unit-test&lt;/em&gt; image, our &lt;code&gt;dotnet test&lt;/code&gt; command is outputting &lt;em&gt;test results&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;However, the test results that &lt;code&gt;dotnet test&lt;/code&gt; creates are stored in the in the container, we need to get them out to our host; otherwise, we can't see them.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;bind volume mount&lt;/strong&gt; option comes into play - we can use the &lt;code&gt;-volume&lt;/code&gt; OR &lt;code&gt;-v&lt;/code&gt; option for short to map the container directory to our host directory when we use, &lt;code&gt;docker run&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For more information about bind volume mount please see, &lt;a href="https://docs.docker.com/engine/reference/commandline/run/#mount-volume--v---read-only" rel="noopener noreferrer"&gt;Docker Docs - docker run - Bind Volume Mount&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our &lt;code&gt;docker run&lt;/code&gt; command can be modified to something like the following:&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;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--rm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;${pwd}&lt;/span&gt;&lt;span class="s2"&gt;\TestResults:/code/test/Samples.WeatherForecast.Api.UnitTest/TestResults/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;samples-weatherforecast-unit-test:v6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Info&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;pwd = &lt;strong&gt;P&lt;/strong&gt;rint &lt;strong&gt;W&lt;/strong&gt;orking &lt;strong&gt;D&lt;/strong&gt;irectory&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now when we execute our &lt;code&gt;docker run&lt;/code&gt; command, we should see the test results file; called, &lt;code&gt;test_results_unit_test.trx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please ensure this works before continuing&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Let's make execution easier
&lt;/h2&gt;

&lt;p&gt;It is a little bit repetitive executing these commands from time to time, sure you can use the test explorer quite a lot, but having to remember these commands and type them out without any typos is rather annoying.&lt;/p&gt;

&lt;p&gt;The best approach is to script it, you can use pretty much anything you want, here as an example, I'm going to use trusty old PowerShell, but feel free to use BASH or even a MAKE file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Script running of unit tests
&lt;/h2&gt;

&lt;p&gt;In your root directory, create a new file called, &lt;code&gt;unit-test.ps1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Paste in the following code:&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="nv"&gt;$IMAGE_NAME_AND_TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"samples-weatherforecast-unit-test:v6"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Unit tests [build]"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unit-test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$IMAGE_NAME_AND_TAG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Unit tests [run]"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--rm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;${pwd}&lt;/span&gt;&lt;span class="s2"&gt;\TestResults:/code/test/Samples.WeatherForecast.Api.UnitTest/TestResults/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$IMAGE_NAME_AND_TAG&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;As you can see, it's pretty simplistic, we are just trying to make things easier for ourselves.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Script the running of the build
&lt;/h3&gt;

&lt;p&gt;Let's do the same thing to our build so we don't have to type &lt;code&gt;docker build&lt;/code&gt; all the time.&lt;/p&gt;

&lt;p&gt;In your root directory, create a new file called, &lt;code&gt;build.ps1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Paste in the following code:&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="nv"&gt;$IMAGE_NAME_AND_TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"samples-weatherforecast:v6"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"App [build]"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$IMAGE_NAME_AND_TAG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  How do I use these scripts?
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Unit tests
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Open&lt;/em&gt; your terminal and &lt;strong&gt;execute&lt;/strong&gt;: &lt;code&gt;.\unit-test.ps1&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This will build your test container, and run your unit tests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Run this now please...&lt;/p&gt;

&lt;p&gt;If all goes well, you should see that the unit test(s) have passed.&lt;/p&gt;

&lt;h4&gt;
  
  
  Build
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Open&lt;/em&gt; your terminal and &lt;strong&gt;execute&lt;/strong&gt;: &lt;code&gt;.\build.ps1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Run this one now too please...&lt;/p&gt;

&lt;p&gt;Again, if all is fine, you should have your container image built.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please make sure your container has been built by double-checking it's there: &lt;code&gt;docker image ls&lt;/code&gt; is your friend here.&lt;/p&gt;

&lt;p&gt;In addition, run your the container and test with Postman or your preferred method.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--rm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;8080:8080&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;samples-weatherforecast:v6&lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  GitHub Actions
&lt;/h2&gt;

&lt;p&gt;We have everything working locally and we are happy, let's move onto our CI; lovely GitHub Actions.&lt;/p&gt;

&lt;p&gt;We will need to add in a some steps to our workflow that we had before.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;env&lt;/code&gt; section in our &lt;code&gt;build-and-push.yaml&lt;/code&gt; file, let's add a new variable called, &lt;code&gt;image-name-unit-tests&lt;/code&gt;, code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/peterjking/samples-weatherforecast-part-6:${{ github.sha }}&lt;/span&gt;
  &lt;span class="na"&gt;image-name-unit-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unit-tests:latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Next up is adding a new step, just below the, &lt;code&gt;checkout repo&lt;/code&gt; step. We need to build our &lt;strong&gt;unit-test runner&lt;/strong&gt; image like so below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unit tests [build]&lt;/span&gt;
        &lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker build --target unit-test -t ${{ env.image-name-unit-tests }} .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Immediately after this step, we need to run the unit tests, please add the step below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unit tests [run]&lt;/span&gt;
        &lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker run --rm -v ${{ github.workspace }}/path/to/artifacts/testresults:/code/test/Samples.WeatherForecast.Api.UnitTest/TestResults ${{ env.image-name-unit-tests }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;You'll notice we are doing the bind volume mount, &lt;code&gt;-v&lt;/code&gt;, but it's different from our local script, this is because of how GitHub Actions works, it seems we cannot make use of &lt;code&gt;pwd&lt;/code&gt;, but thankfully, GitHub provides built-in variables we can take advantage of, in our case, we will use the, &lt;code&gt;github.workspace&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The GitHub workspace directory path. The workspace directory is a copy of your repository if your workflow uses the actions/checkout action. If you don't use the actions/checkout action, the directory will be empty. For example, /home/runner/work/my-repo-name/my-repo-name.&lt;/p&gt;

&lt;p&gt;For more information, please see, &lt;a href="https://docs.github.com/en/actions/reference/environment-variables" rel="noopener noreferrer"&gt;GitHub Docs - Environment variables&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;We have the test results file in &lt;code&gt;trx&lt;/code&gt; format on this &lt;em&gt;GitHub Actions Runner Host&lt;/em&gt;. It would be nice if we can persist this against this build job... and we can! We can use the &lt;a href="https://github.com/actions/upload-artifact" rel="noopener noreferrer"&gt;upload artifact v2 action&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unit tests [results]&lt;/span&gt;  
        &lt;span class="s"&gt;uses&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v2&lt;/span&gt;
        &lt;span class="s"&gt;if&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unit-test-results&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workspace }}/path/to/artifacts/testresults/test_results_unit_test.trx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Commit your changes now, and ensure everything works.&lt;/p&gt;

&lt;p&gt;As soon as you commit your workflow file, the build should kick-off, your unit tests image should be build, run the image, output the tests results, and store the test results against the build job itslef.&lt;/p&gt;

&lt;p&gt;You should see a screen like this:&lt;/p&gt;

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




&lt;h2&gt;
  
  
  We can do better!
&lt;/h2&gt;

&lt;p&gt;Can we? Yes, we sure can... It's great we have an artifact stored against the job, but it's rather painful and annoying to download it, and open the trx file, or look through the build log.&lt;/p&gt;

&lt;p&gt;There are a bunch of GitHub Actions that can help us out here, for me I've chosen, &lt;a href="https://github.com/dorny/test-reporter" rel="noopener noreferrer"&gt;GitHub Actions - Test Reporter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Immediately after our artifact upload step, let's create another one like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unit tests [publish]&lt;/span&gt;
        &lt;span class="s"&gt;uses&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dorny/test-reporter@v1&lt;/span&gt;
        &lt;span class="s"&gt;if&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unit tests&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workspace }}/path/to/artifacts/testresults/test_results_unit_test.trx&lt;/span&gt;
          &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet-trx&lt;/span&gt;
          &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This action will carry out some magic for us.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;token: ${{ secrets.GITHUB_TOKEN }}&lt;/code&gt; is an in-built, predefined secret that gives access to the build job.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Please commit this change, you'll build should kick-off once again, and hopefully you'll see the test results nicely formatted.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  What have we learned?
&lt;/h2&gt;

&lt;p&gt;We have learned how to subtly change our image layering to ensure the Docker build cache is as optimized as possible; yes, it's more code to write, but you'll save build time in the end, especially if your solution grows.&lt;/p&gt;

&lt;p&gt;We've added a new unit test project using the commandline and hooked up the references, build an incredibly basic unit test (in principle).&lt;/p&gt;

&lt;p&gt;We have taken advantage of the &lt;code&gt;--target&lt;/code&gt; option to build a container image up to a certain point in our Dockerfile; in our case, we have our unit test runner. We have even scripted it to make our lives a litter easier too with a few lines of good old Powershell :)&lt;/p&gt;

&lt;p&gt;On top of this we have modified our GitHub Actions' Workflow in a similar vein to how we are executing locally; we build the unit test image, run the unit test using Docker, and build our final image.&lt;/p&gt;

&lt;p&gt;Finally, we have persisted the unit test results as an artifact and have a nicely formatted report against our build job.&lt;/p&gt;




&lt;h2&gt;
  
  
  Next up
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-7-4m71"&gt;Part 7&lt;/a&gt; in this series will be about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code coverage&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  More information
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.azure.com" rel="noopener noreferrer"&gt;https://www.azure.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/" rel="noopener noreferrer"&gt;https://dotnet.microsoft.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.github.com" rel="noopener noreferrer"&gt;https://www.github.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com" rel="noopener noreferrer"&gt;https://www.docker.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com" rel="noopener noreferrer"&gt;https://www.pulumi.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>docker</category>
      <category>csharp</category>
      <category>github</category>
    </item>
    <item>
      <title>API's From Dev to Production - Part 5 - Healthchecks</title>
      <dc:creator>Pete King</dc:creator>
      <pubDate>Fri, 12 Mar 2021 17:45:07 +0000</pubDate>
      <link>https://dev.to/peteking/api-s-from-dev-to-production-part-5-26cp</link>
      <guid>https://dev.to/peteking/api-s-from-dev-to-production-part-5-26cp</guid>
      <description>&lt;h2&gt;
  
  
  Series Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to &lt;strong&gt;Part 5&lt;/strong&gt; of this blog series that will go from the most basic example of a .net 5 webapi in C#, and the journey from development to production with a &lt;a href="https://dev.to/peteking/shift-left-engineering-4fpp"&gt;&lt;strong&gt;shift-left&lt;/strong&gt;&lt;/a&gt; mindset. We will use &lt;strong&gt;Azure&lt;/strong&gt;, &lt;strong&gt;Docker&lt;/strong&gt;, &lt;strong&gt;GitHub&lt;/strong&gt;, &lt;strong&gt;GitHub Actions&lt;/strong&gt; for CI/C-Deployment and Infrastructure as Code using &lt;strong&gt;Pulumi&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this post we will be looking at &lt;strong&gt;CIS Issues&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add a healthcheck to the Dockerfile&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add an item to the CIS allowedlist.yaml&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Healthchecks are important to ensure you are aware of the state of your services, particularly when you have monitoring solutions that inform you if something is not quite right. No one wants customers telling you your product doesn’t work when there are adequate ways to deal with incidents before they could potentially impact customers.&lt;/p&gt;

&lt;p&gt;It can be good to have the Docker healthcheck instruction, but not strictly necessary when we are not running Docker Swam, but can be handing with Docker Compose - Therefore, I recommend adding it, but maybe comment it out, add &lt;strong&gt;CIS-DI-006&lt;/strong&gt; to the &lt;code&gt;allowedlist.yaml&lt;/code&gt; so it is ignored by the container scan.&lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/peteking" rel="noopener noreferrer"&gt;
        peteking
      &lt;/a&gt; / &lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-5" rel="noopener noreferrer"&gt;
        Samples.WeatherForecast-Part-5
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This repository is part of the blog post series, API's from Dev to Production - Part 5 on dev.to. Based on the standard .net standard Weather API sample.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;





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

&lt;p&gt;In Part 4 we were able to get into security concerns and looked into both container scanning and CIS. We solved the the very important &lt;strong&gt;CIS-DI-001&lt;/strong&gt;. Here we will look further into &lt;strong&gt;CIS-DI-006&lt;/strong&gt;.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;We will be picking-up where we left off in &lt;a href=""&gt;Part 4&lt;/a&gt;, which means you’ll need the end-result from &lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-4" rel="noopener noreferrer"&gt;GitHub Repo - Part 4&lt;/a&gt; to start with.&lt;/p&gt;

&lt;p&gt;If you have followed this series all the way through, and I would encourage you to do so, but it isn't necessary if previous posts are knowledge to you already.&lt;/p&gt;




&lt;h2&gt;
  
  
  Add HEALTHCHECK
&lt;/h2&gt;

&lt;p&gt;Healthchecks are an important piece of surfacing health of components of a system, you might ask why you’d want to know? Well, if you service is not servicing requests you’d want to know before your customers staff calling you!   &lt;/p&gt;

&lt;p&gt;.NET 5 has some cool built in functionality to help with all the boilerplate of adding healthchecks, but remember, the Dockle tool itself is not checking for that. It’s actually checking for the HEALHCHECK instruction in the Dockerfile.&lt;/p&gt;

&lt;p&gt;….BUT, we still need a healthcheck in our API, otherwise, there’s nothing to actually check!   &lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;src&lt;/code&gt; folder, the .NET 5 API, you should be familiar with the &lt;code&gt;Startup.cs&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Below is an snippet, and we need to add in line &lt;code&gt;4&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHealthChecks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSwaggerGen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SwaggerDoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenApiInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Samples.WeatherForecast.Api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"v1"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Another place we need to modify is further down, I’ve added in line &lt;code&gt;19&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IWebHostEnvironment&lt;/span&gt; &lt;span class="n"&gt;env&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="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseDeveloperExceptionPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSwagger&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSwaggerUI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SwaggerEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/swagger/v1/swagger.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Samples.WeatherForecast.Api v1"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseHttpsRedirection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseRouting&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapHealthChecks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/health"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;That’s all there is to it on the .NET side of things, however, Docker won’t call it yet - So we need to update our &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Final stage/image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/dotnet/runtime-deps:${VERSION}&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-g&lt;/span&gt; 1000 dotnet &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    adduser &lt;span class="nt"&gt;-u&lt;/span&gt; 1000 &lt;span class="nt"&gt;-G&lt;/span&gt; dotnet &lt;span class="nt"&gt;-s&lt;/span&gt; /bin/sh &lt;span class="nt"&gt;-D&lt;/span&gt; dotnet

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=dotnet:dotnet --from=publish /out .&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; ASPNETCORE_URLS=http://*:8080&lt;/span&gt;

&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=60s --timeout=3s --retries=3 \&lt;/span&gt;
    CMD wget localhost:8080/health -q -O - &amp;gt; /dev/null 2&amp;gt;&amp;amp;1

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; dotnet&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["./Samples.WeatherForecast.Api"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above is a snippet (the lower half of our &lt;code&gt;Dockerfile&lt;/code&gt;), we’ve added in line &lt;code&gt;12&lt;/code&gt; &amp;amp; &lt;code&gt;13&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For more information about the HEALTHCHECK instruction, please see, &lt;a href="https://docs.docker.com/engine/reference/builder/#healthcheck" rel="noopener noreferrer"&gt;Docker Docs - Builder - Healthcheck&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the options we are using, we have set the interval to 60 seconds, a timeout of 3 seconds, and we retry 3 times if it fails. On the continued line &lt;code&gt;13&lt;/code&gt; we execute a command.&lt;/p&gt;

&lt;p&gt;You may be wondering why we are simply not just using &lt;code&gt;curl&lt;/code&gt;, well &lt;code&gt;curl&lt;/code&gt; would be great, however, &lt;em&gt;it’s isn’t even installed&lt;/em&gt;! This is how stripped-back this container image is, if we wanted to use &lt;code&gt;curl&lt;/code&gt; we’ve have use &lt;code&gt;apt-get&lt;/code&gt; etc.&lt;/p&gt;

&lt;p&gt;So we are using &lt;code&gt;wget&lt;/code&gt; instead, where we call our &lt;code&gt;/health&lt;/code&gt; endpoint and pipe out the response check the values for output.&lt;/p&gt;




&lt;h2&gt;
  
  
  Build &amp;amp; test time!
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;docker build -t samples-weatherforecast:v5 .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker run -it --rm -p 8080:8080 samples-weatherforecast:v5&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once it’s running you’ll want to check the status of the container, you can do this with the following command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker container ls&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Remember, you have a minute before it starts checking!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you’re quick, you’ll see something like the below, note the status is, starting.&lt;/p&gt;

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




&lt;blockquote&gt;
&lt;p&gt;After 60 seconds, we should see a status of healthy.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;&lt;strong&gt;It works!&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What have we learned?
&lt;/h2&gt;

&lt;p&gt;We have learned the very basics and simple steps to add a minimal &lt;strong&gt;healthcheck&lt;/strong&gt; to our API, and ensure Docker will call this and mitigate &lt;strong&gt;CIS-DI-006&lt;/strong&gt;; however, the big question is, do we need the &lt;code&gt;HEALTHCHECK&lt;/code&gt; instruction if we are going to be hosting this on anything other than &lt;em&gt;Docker Swarm&lt;/em&gt; OR &lt;em&gt;Docker Compose&lt;/em&gt; - My view is we actually don’t! &lt;/p&gt;

&lt;p&gt;But… We should have a &lt;strong&gt;healthcheck&lt;/strong&gt; in our &lt;strong&gt;API&lt;/strong&gt;, because whatever we host it in, in our case it will be &lt;strong&gt;Azure’s Web App for Containers&lt;/strong&gt;, we will need a &lt;strong&gt;healthcheck&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;So you may think this is a pointless exercise, but not really, we’ve got the API healthcheck, but we can safely remove the &lt;code&gt;HEALTHCHECK&lt;/code&gt; OR comment-out the instructions from our &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Up next
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-6-1p34"&gt;Part 6&lt;/a&gt; in this series will be about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unit Testing - with Docker and GitHub Actions&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  More information
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.azure.com" rel="noopener noreferrer"&gt;https://www.azure.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/" rel="noopener noreferrer"&gt;https://dotnet.microsoft.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.github.com" rel="noopener noreferrer"&gt;https://www.github.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com" rel="noopener noreferrer"&gt;https://www.docker.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com" rel="noopener noreferrer"&gt;https://www.pulumi.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>docker</category>
      <category>csharp</category>
      <category>github</category>
    </item>
    <item>
      <title>API's From Dev to Production - Part 4 - Image Scanning</title>
      <dc:creator>Pete King</dc:creator>
      <pubDate>Fri, 05 Mar 2021 12:20:11 +0000</pubDate>
      <link>https://dev.to/peteking/api-s-from-dev-to-production-part-4-49g8</link>
      <guid>https://dev.to/peteking/api-s-from-dev-to-production-part-4-49g8</guid>
      <description>&lt;h2&gt;
  
  
  Series Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to Part 4 of this blog series that will go from the most basic example of a .net 5 webapi in C#, and the journey from development to production with a &lt;a href="https://dev.to/peteking/shift-left-engineering-4fpp"&gt;&lt;strong&gt;shift-left&lt;/strong&gt;&lt;/a&gt; mindset. We will use &lt;strong&gt;Azure&lt;/strong&gt;, &lt;strong&gt;Docker&lt;/strong&gt;, &lt;strong&gt;GitHub&lt;/strong&gt;, &lt;strong&gt;GitHub Actions&lt;/strong&gt; for CI/C-Deployment and Infrastructure as Code using &lt;strong&gt;Pulumi&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this post we will be looking at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add container scanning to our GitHub Action workflow&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Resolve security vulnerabilities&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;We add the &lt;strong&gt;Azure Container&lt;/strong&gt; Scan GitHub Action to our actions' workflow file, before pushing the container image to our registry. Add a group and user to our &lt;code&gt;Dockerfile&lt;/code&gt; and use the &lt;code&gt;USER&lt;/code&gt; command to specify the user for the process to run; ensuring our copied files can be accessed by this user. The security attack surface is reduced by running as non-root, and this is raised in CIS; the linting provided by &lt;strong&gt;Dockle&lt;/strong&gt;. We resolved some slightly older CVE’s as examples; these can be raised by &lt;strong&gt;Trivy&lt;/strong&gt; and can be allowed through or not based on configuration. Both Trivy and Dockle are wrapped by the Azure Container Scan GitHub Action.&lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/peteking" rel="noopener noreferrer"&gt;
        peteking
      &lt;/a&gt; / &lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-4" rel="noopener noreferrer"&gt;
        Samples.WeatherForecast-Part-4
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This repository is part of the blog post series, API's from Dev to Production - Part 4 on dev.to. Based on the standard .net standard Weather API sample.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;





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

&lt;p&gt;In Part 3 we were able to get into GitHub Actions and build and push our Docker image to the GitHub Container registry. This is a great start, we are producing a Docker image in CI and it’s a consistent image, however, &lt;strong&gt;what about vulnerabilities&lt;/strong&gt;?&lt;/p&gt;




&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;We will be picking-up where we left off in &lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-3-7dn"&gt;Part 3&lt;/a&gt;, which means you’ll need the end-result from &lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-3" rel="noopener noreferrer"&gt;GitHub Repo - Part 3&lt;/a&gt; to start with.&lt;/p&gt;

&lt;p&gt;If you have followed this series all the way through, and I would encourage you to do so, but it isn't necessary if previous posts are knowledge to you already.&lt;/p&gt;




&lt;h2&gt;
  
  
  Vulnerabilities
&lt;/h2&gt;

&lt;p&gt;A software vulnerability is a glitch, flaw, or weakness that is present in software or in an Operating System. CVE (&lt;strong&gt;C&lt;/strong&gt;ommon &lt;strong&gt;V&lt;/strong&gt;ulnerabilities and &lt;strong&gt;E&lt;/strong&gt;xposures), contains a &lt;a href="https://cve.mitre.org/cve/" rel="noopener noreferrer"&gt;list&lt;/a&gt; of records each containing an identification number, a description, and at least one public reference - for publicly known cybersecurity vulnerabilities.&lt;/p&gt;

&lt;p&gt;CVE Records are used in numerous cybersecurity &lt;a href="https://cve.mitre.org/about/faqs.html#what_types_of_products_use_cve" rel="noopener noreferrer"&gt;products and services&lt;/a&gt; from around the world, including the U.S. National Vulnerability Database &lt;a href="https://cve.mitre.org/about/cve_and_nvd_relationship.html" rel="noopener noreferrer"&gt;(NVD)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are many open source and commercial tools specifically designed for CI purposes; to be used before pushing a Docker image to a container registry. This can ensure any CVE’s of certain thresholds can be caught a build time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Commercial tools can also scan containers at runtime and apply custom mediating actions.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  CIS (Center for Internet Security)
&lt;/h2&gt;

&lt;p&gt;CIS is a community of cybersecurity experts, who have developed CIS Benchmarks: more than 100 configuration guidelines across 25+ vendor product families to safeguard systems against today’s evolving cyber threats.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CIS also produced a list of hardened images - &lt;a href="https://www.cisecurity.org/cis-hardened-image-list/" rel="noopener noreferrer"&gt;https://www.cisecurity.org/cis-hardened-image-list/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why is this important?
&lt;/h2&gt;

&lt;p&gt;It’s important because the software we design, build, and ship needs to be secure, we want to ensure safety of our end users' data and be responsible. We &lt;strong&gt;must&lt;/strong&gt; take security seriously and both CVE and CIS provide a trusted source of information.&lt;/p&gt;

&lt;p&gt;The open source community has done an amazing job of utilising the information and provided fantastic tooling we can all take advantage of.&lt;/p&gt;




&lt;h2&gt;
  
  
  What can we do?
&lt;/h2&gt;

&lt;p&gt;In terms of CVE’s, &lt;a href="https://www.aquasec.com" rel="noopener noreferrer"&gt;AquaSec&lt;/a&gt; has created not only a great commercial product but an open source product called &lt;a href="https://github.com/aquasecurity/trivy" rel="noopener noreferrer"&gt;Trivy&lt;/a&gt;. This is a Docker image scanner that detects vulnerabilities of OS packages (Alpine, RHEL, CentOS, etc.) and application dependencies (Bundler, Composer, npm, yarn, etc.). Trivy is easy to use, you can just install the binary and scan, all that’s required is to specify a target such as an image name of the container.&lt;/p&gt;

&lt;p&gt;For CIS, there is an open source tool called &lt;a href="https://github.com/goodwithtech/dockle" rel="noopener noreferrer"&gt;Dockle&lt;/a&gt; - Dockle is a Container Image Linter for Security, Helping build the Best-Practice Docker Image, which is ideal for CI.&lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub Actions to the rescue
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Microsoft&lt;/strong&gt; has create a GitHub Action which wraps both &lt;em&gt;Trivy&lt;/em&gt; and &lt;em&gt;Dockle&lt;/em&gt; called container-image-scan, you can find it on the GitHub Actions Marketplace and the open source repo, &lt;a href="https://github.com/Azure/container-scan" rel="noopener noreferrer"&gt;Azure/container-scan&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As of writing, the GitHub Container Registry does not scan images you push there, but other container registries do, such as the Azure Container Registry (ACR). It’s good that images will be scanned once they are in the registry, however, in terms of shifting-left, it is hugely beneficial to scan images during CI, if a vulnerability is found during build, engineers in the team are notified immediately, they can fix it themselves or consult with experts to help them. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This promotes a faster feedback loop and potentially stops images being pushed to a registry if that have vulnerabilities.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Let’s use Azure Container Scan
&lt;/h2&gt;

&lt;p&gt;In out workflow file we previously called, &lt;code&gt;build-and-publish.yml&lt;/code&gt;, it’s located in the &lt;code&gt;.gihub/workflows&lt;/code&gt; folder. We can add an extra step, we’ve given it a name, &lt;code&gt;Scan docker image&lt;/code&gt;, specified the &lt;code&gt;azure/container-scan@v0&lt;/code&gt; GitHub Action, and set the required and optional parameters. We’ve set the &lt;code&gt;severity-threshold: MEDIUM&lt;/code&gt;, this is completely up to you, for this example, I’ve simply chosen MEDIUM. Please read the documentation on the Azure Container Scan repository and set it to what you believe is adequate for you specific situation.&lt;/p&gt;

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

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Scan docker image&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/container-scan@v0&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.image-name }}&lt;/span&gt;
    &lt;span class="na"&gt;severity-threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MEDIUM&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.repository_owner }}&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GH_CR }}&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Full Workflow file
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Publish&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/peterjking/samples-weatherforecast-part-4:${{ github.sha }}&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout the repo&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build docker image&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker build . -t ${{ env.image-name }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Scan docker image&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/container-scan@v0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;image-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.image-name }}&lt;/span&gt;
          &lt;span class="na"&gt;severity-threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MEDIUM&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.repository_owner }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GH_CR }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to GitHub Container Registry&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.repository_owner }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GH_CR }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Push docker image&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker push ${{ env.image-name }}&lt;/span&gt;


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

&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;If you commit the changes, your GitHub Action will kick-off and build and scan.&lt;/p&gt;

&lt;p&gt;If you have not specified your repository secret GH_CR because you may be using a new repository perhaps for this, don’t forget to add the secret to your repo.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h3&gt;
  
  
  Scan results
&lt;/h3&gt;
&lt;h4&gt;
  
  
  CVE
&lt;/h4&gt;

&lt;p&gt;Depending on when you are running this and what the state of the base image will determine what you see. For me at the time of writing, I have no CVE’s, which is great! Only a week before writing this, I made a change for a similar workflow and had a HIGH CVE, it was all to do with OpenSSL in Alpine Linux. The screenshot is below for when it came up.&lt;/p&gt;

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



&lt;p&gt;We can check what CVE this is with its number, a quick Google will reveal some interesting information.&lt;/p&gt;

&lt;p&gt;For this particular issue, I found the following link: &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/docker-library/official-images/pull/9295" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        alpine: bump 3.12.3/3.11.7 (CVE-2020-1971)
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#9295&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/ncopa" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F66357%3Fv%3D4" alt="ncopa avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/ncopa" rel="noopener noreferrer"&gt;ncopa&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/docker-library/official-images/pull/9295" rel="noopener noreferrer"&gt;&lt;time&gt;Dec 16, 2020&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      
    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/docker-library/official-images/pull/9295" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Whilst writing this blog series, this issue was open.&lt;br&gt;
Since then, the issue has been closed and merged :)&lt;/p&gt;

&lt;p&gt;It was essentially a simple update, OpenSSL needed to be updated in Alpine Linux to resolve this vulnerability.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since we have zero &lt;em&gt;known&lt;/em&gt; vulnerabilities at the moment, from another API, I did come across another issue, again, with Alpine but earlier in December 2020. The scan results are below as another example:&lt;/p&gt;

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




&lt;p&gt;In early December this came up, and this is because I was using the same base image we are using in this blog post. You should always investigate CVE and determine if they apply to you, and the reason why I’m showing you this is for that reason. Let’s go through it and see what it is.&lt;/p&gt;

&lt;p&gt;Again, a quick Google on CVE-2020-28928, and I decide to settle on the information over at AquaSec:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://avd.aquasec.com/nvd/cve-2020-28928/" rel="noopener noreferrer"&gt;https://avd.aquasec.com/nvd/cve-2020-28928/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see it is indeed MEDIUM, 5.5 severity, and when it was published, November 24th 2020.&lt;/p&gt;

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




&lt;p&gt;If you scroll down the page you’ll see a nice thoughtful list on mitigations. If you look even closer you can see the potential mitigation is essentially memory management. Luckily for us, we are using a language and framework which prides itself on being memory safe and good memory management (on our behalf of course). Given we are using C# in this case and we are not using C or C++ for instance and using string.h etc.  We can safely ignore this. &lt;/p&gt;

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




&lt;p&gt;How do we ignore this CVE you ask? Well, if you check the documentation on the Azure Container Scan repository, it shows you how…&lt;/p&gt;

&lt;p&gt;Simply create a new folder in your &lt;code&gt;.github/workflows/&lt;/code&gt; called &lt;code&gt;containerscan&lt;/code&gt;, in there create a new YAML file called, &lt;code&gt;allowedlist.yaml&lt;/code&gt;.&lt;/p&gt;

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

&lt;span class="na"&gt;general&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vulnerabilities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CVE-2020-28928&lt;/span&gt;
  &lt;span class="na"&gt;bestPracticeViolations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;


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

&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Good Practice&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you do this, you need to have a regular review going forward of what you are allowing through.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Given the noted good practice above, this is a &lt;em&gt;suburb example&lt;/em&gt;, Alpine Linux resolved this issue. How did I know this? Well, looking at Alpine’s GitHub repo tells all in this GitHub issue: &lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/alpinelinux/docker-alpine/issues/123" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        alpine:latest has CVE-2020-28928
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#123&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/jerome-laforge" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F9979375%3Fv%3D4" alt="jerome-laforge avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/jerome-laforge" rel="noopener noreferrer"&gt;jerome-laforge&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/alpinelinux/docker-alpine/issues/123" rel="noopener noreferrer"&gt;&lt;time&gt;Dec 04, 2020&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;musl : 1.1.24-r9 -
Layer: sha256:ace0eda3e3be35a979cec764a3321b4c7d0b9e4bb3094d20d3ff6782961a8d54&lt;/p&gt;
&lt;p&gt;CVE-2020-28928
In musl libc through 1.2.1, wcsnrtombs mishandles particular combinations of destination buffer size and source character limit, as demonstrated by an invalid write access (buffer overflow).
Solution: Upgrade musl to 1.1.24-r10&lt;/p&gt;
&lt;p&gt;References:
&lt;a href="http://www.openwall.com/lists/oss-security/2020/11/20/4" rel="nofollow noopener noreferrer"&gt;http://www.openwall.com/lists/oss-security/2020/11/20/4&lt;/a&gt;
&lt;a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-28928" rel="nofollow noopener noreferrer"&gt;https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-28928&lt;/a&gt;
&lt;a href="https://lists.debian.org/debian-lts-announce/2020/11/msg00050.html" rel="nofollow noopener noreferrer"&gt;https://lists.debian.org/debian-lts-announce/2020/11/msg00050.html&lt;/a&gt;
&lt;a href="https://musl.libc.org/releases.html" rel="nofollow noopener noreferrer"&gt;https://musl.libc.org/releases.html&lt;/a&gt;&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/alpinelinux/docker-alpine/issues/123" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Now, for my repo or a repo that had this vulnerability, I can safely remove it from the, &lt;code&gt;allowedlist.yaml&lt;/code&gt; file.&lt;/p&gt;

&lt;h4&gt;
  
  
  CIS
&lt;/h4&gt;

&lt;p&gt;In terms of CIS, we have a single 1 x WARN, and 2 x INFO.&lt;/p&gt;

&lt;p&gt;Personally, I believe CIS-DI-0001 should really be higher, you &lt;strong&gt;really should&lt;/strong&gt; in my view run your process as &lt;strong&gt;non-root&lt;/strong&gt;. It’s easily solved too!&lt;/p&gt;

&lt;p&gt;When the processes inside the container run as root, the same as the host machine, this give unprecedented privileges; we should always opt for least-privilege as a matter of practice. For example, if a user is able to gain access to the host as root &lt;/p&gt;

&lt;p&gt;By default, root in a container is the same root (&lt;code&gt;uid 0&lt;/code&gt;) as the host machine. If a user succeeds in breaking out of an application running as root in a container, this malicious user may be able to gain access to the host with the same root user.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Good Practice&lt;/strong&gt;&lt;br&gt;
It is good practice to launch processes with a non-root user.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TIP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some official images already create a user for you to use, it’s always good to inspect the &lt;code&gt;Dockerfile&lt;/code&gt; your using as your base to see.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In our case, a user is not created for us, so let’s fix that now, you can add the following code to your &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;

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

&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-g&lt;/span&gt; 1000 dotnet &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    adduser &lt;span class="nt"&gt;-u&lt;/span&gt; 1000 &lt;span class="nt"&gt;-G&lt;/span&gt; dotnet &lt;span class="nt"&gt;-s&lt;/span&gt; /bin/sh &lt;span class="nt"&gt;-D&lt;/span&gt; dotnet


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

&lt;/div&gt;

&lt;p&gt;Once we do this, when the process runs, it should be running as this, &lt;code&gt;dotnet&lt;/code&gt; user. Therefore, we should also make sure when we &lt;code&gt;COPY&lt;/code&gt; files we change the owner to the &lt;code&gt;dotnet&lt;/code&gt; user using &lt;code&gt;chown&lt;/code&gt; like so:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;COPY --chown=dotnet:dotnet --from=publish /out .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If we do &lt;strong&gt;not&lt;/strong&gt; do this, we run the risk of the &lt;code&gt;dotnet&lt;/code&gt; user not being able to see the files and execute - &lt;em&gt;That would be bad for us&lt;/em&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Full Dockerfile
&lt;/h4&gt;

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

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; VERSION=5.0-alpine&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/dotnet/sdk:${VERSION} AS build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Copy and restore as distinct layers&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app/src/Samples.WeatherForecast.Api&lt;/span&gt;
RUN dotnet restore Samples.WeatherForecast.Api.csproj -r linux-musl-x64

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; build AS publish&lt;/span&gt;
RUN dotnet publish \
    -c Release \
    -o /out \
    -r linux-musl-x64 \
    --self-contained=true \
    --no-restore \
    -p:PublishReadyToRun=true \
    -p:PublishTrimmed=true

&lt;span class="c"&gt;# Final stage/image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/dotnet/runtime-deps:${VERSION}&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-g&lt;/span&gt; 1000 dotnet &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    adduser &lt;span class="nt"&gt;-u&lt;/span&gt; 1000 &lt;span class="nt"&gt;-G&lt;/span&gt; dotnet &lt;span class="nt"&gt;-s&lt;/span&gt; /bin/sh &lt;span class="nt"&gt;-D&lt;/span&gt; dotnet

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=dotnet:dotnetgroup --from=publish /out .&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; dotnet&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; ASPNETCORE_URLS=http://+:8080&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["./Samples.WeatherForecast.Api"]&lt;/span&gt;


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

&lt;/div&gt;




&lt;p&gt;Now you have a slightly refactored &lt;code&gt;Dockerfile&lt;/code&gt;, let’s give this a go locally to make sure all is well.&lt;/p&gt;

&lt;p&gt;Build the image locally:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker build -t samples-weatherforecast:v4 .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Run the image locally:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker run -it --rm -p 8080:8080 samples-weatherforecast:v4&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Test the image in Postman or REST Client - &lt;a href="https://marketplace.visualstudio.com/items?itemName=humao.rest-client" rel="noopener noreferrer"&gt;https://marketplace.visualstudio.com/items?itemName=humao.rest-client&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you’ve verified that it works, it’s now time to commit those changes. This should kick-off the GitHub Action and build, scan and push the Docker image container registry.&lt;/p&gt;

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




&lt;p&gt;We can verify our API is running under our dotnet user but getting inside the container.&lt;/p&gt;

&lt;p&gt;How you ask? Well, there are two ways, the &lt;strong&gt;CLI&lt;/strong&gt; way or you can use &lt;strong&gt;Docker Desktop&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I’ll &lt;em&gt;show off&lt;/em&gt; the Docker Desktop feature.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Open&lt;/em&gt; &lt;strong&gt;Docker Desktop&lt;/strong&gt; → &lt;em&gt;Move&lt;/em&gt; you mouse of your running container → &lt;em&gt;Click&lt;/em&gt;, &lt;strong&gt;CLI&lt;/strong&gt;.&lt;/p&gt;

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




&lt;p&gt;A command prompt should open and you should be inside your container!&lt;/p&gt;

&lt;p&gt;Now, &lt;em&gt;execute&lt;/em&gt;, &lt;code&gt;whoami&lt;/code&gt; and it should come back saying, &lt;code&gt;dotnet&lt;/code&gt;; this is your &lt;code&gt;USER&lt;/code&gt;.&lt;/p&gt;

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




&lt;p&gt;You can also execute the &lt;code&gt;top&lt;/code&gt; command, let’s do this too:&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Cool! All running as &lt;code&gt;dotnet&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h4&gt;
  
  
  Build results
&lt;/h4&gt;

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




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




&lt;blockquote&gt;
&lt;p&gt;Packages → Container Registry&lt;br&gt;
Let’s double check that our image was pushed by checking our GitHub Packages area.&lt;/p&gt;
&lt;/blockquote&gt;

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




&lt;p&gt;&lt;strong&gt;&lt;em&gt;Success!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In addition, I have also pulled this image down and tested it too just like we did in &lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-3-7dn"&gt;API's From Dev to Production - Part 3&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What have we learned?
&lt;/h2&gt;

&lt;p&gt;We have learned how to scan our container images before we end up pushing them to our container registry. We understand how important this is and why we should do it, the open source tools that are available to you. We have also learned how to decrease our security attack surface by running our process as a user by adding a group and user in our Dockerfile.&lt;/p&gt;

&lt;p&gt;There are many open source tools that are very capable, however, at times you may wish to opt for a paid product.&lt;/p&gt;

&lt;p&gt;Here is a short list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://snyk.io/" rel="noopener noreferrer"&gt;Snyk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.aquasec.com/" rel="noopener noreferrer"&gt;Acqua&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.paloaltonetworks.com/prisma/cloud" rel="noopener noreferrer"&gt;Prisma Cloud&lt;/a&gt; (formally Twistlock)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://anchore.com/" rel="noopener noreferrer"&gt;Anchore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;...and many more, don't forget some container registries also scan images.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Up next
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-5-26cp"&gt;Part 5&lt;/a&gt; in this series will be about &lt;strong&gt;CIS Issues&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add a healthcheck to the Dockerfile&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add an item to the CIS allowedlist.yaml&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  More information
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.azure.com" rel="noopener noreferrer"&gt;https://www.azure.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/" rel="noopener noreferrer"&gt;https://dotnet.microsoft.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.github.com" rel="noopener noreferrer"&gt;https://www.github.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com" rel="noopener noreferrer"&gt;https://www.docker.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com" rel="noopener noreferrer"&gt;https://www.pulumi.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>docker</category>
      <category>csharp</category>
      <category>github</category>
    </item>
    <item>
      <title>API's From Dev to Production - Part 3 - GitHub Actions</title>
      <dc:creator>Pete King</dc:creator>
      <pubDate>Fri, 26 Feb 2021 11:56:33 +0000</pubDate>
      <link>https://dev.to/peteking/api-s-from-dev-to-production-part-3-7dn</link>
      <guid>https://dev.to/peteking/api-s-from-dev-to-production-part-3-7dn</guid>
      <description>&lt;h2&gt;
  
  
  Series Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to Part 3 of this blog series that will go from the most basic example of a .net 5 webapi in C#, and the journey from development to production with a &lt;a href="https://dev.to/peteking/shift-left-engineering-4fpp"&gt;&lt;strong&gt;shift-left&lt;/strong&gt;&lt;/a&gt; mindset. We will use &lt;strong&gt;Azure&lt;/strong&gt;, &lt;strong&gt;Docker&lt;/strong&gt;, &lt;strong&gt;GitHub&lt;/strong&gt;, &lt;strong&gt;GitHub Actions&lt;/strong&gt; for CI/C-Deployment and Infrastructure as Code using &lt;strong&gt;Pulumi&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this post we will be looking at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Actions:

&lt;ul&gt;
&lt;li&gt;Build the docker image&lt;/li&gt;
&lt;li&gt;Publish the docker image to the GitHub Container Registry&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Instead of building locally like we did previously, we utilised GitHub Actions and the GitHub Container Registry to store our Docker image. GitHub Actions workflow is basic YAML, and we used a few actions to achieve what we needed. We checked-out the repository, docker build, logged into our GitHub Container Registry and executed a docker push command. We used a Personal Access token and stored it in GitHub secrets to use in our workflow, and finally, we pulled down our public image from the GitHub Container Registry and tested locally using Postman.&lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/peteking" rel="noopener noreferrer"&gt;
        peteking
      &lt;/a&gt; / &lt;a href="https://github.com/peteking/Samples.WeatherForecast-Part-3" rel="noopener noreferrer"&gt;
        Samples.WeatherForecast-Part-3
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This repository is part of the blog post series, API's from Dev to Production - Part 3 on dev.to. Based on the standard .net standard Weather API sample.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;We will be picking-up where we left off in &lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-2-1kcn"&gt;Part 2&lt;/a&gt;, which means you’ll need the end-result from &lt;a href="https://github.com/PeterJKing/Samples.WeatherForecast-Part-2" rel="noopener noreferrer"&gt;GitHub Repo from - Part 2&lt;/a&gt; to start with.&lt;/p&gt;




&lt;h2&gt;
  
  
  What are GitHub Actions?
&lt;/h2&gt;

&lt;p&gt;GitHub Actions makes it easy to automate all kinds of software workflows, right inside GitHub. You can build, test, and deploy your code and more. Workflows can be triggered off many GitHub events like push, issue creation and more. There is support for Linux, macOS, Windows, ARM and of course containers   Even matrix builds - so you can build the same software across multiple OS’s. There are live logs, secret store and more, plus the UX is rather nice too! All of this is driven by YAML files, nice and neat, and stored under source control management.&lt;/p&gt;

&lt;p&gt;There is also GitHub Packages which has various package management providers like npm, nuget and more. For containers, there is a new GitHub Container Registry, so images no longer need to be stored in GitHub Packages but there is a real container registry - We will use this in this blog post :D&lt;/p&gt;




&lt;h2&gt;
  
  
  Let’s get started
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm3wqredwqvomhlj6l2np.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm3wqredwqvomhlj6l2np.png" alt="GitHub Actions - New"&gt;&lt;/a&gt;&lt;br&gt;
If you navigate to GitHub Actions in your repository, you’ll be presented with the above screen. There are various starter workflows or you can simply start from scratch.&lt;/p&gt;


&lt;h2&gt;
  
  
  Build your Workflow from scratch
&lt;/h2&gt;

&lt;p&gt;We’re going to build it from scratch, there isn’t much to it, and it’s easier to learn and see what happening this way.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Select&lt;/em&gt; → &lt;strong&gt;&lt;em&gt;set up a workflow yourself&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You will see a default workflow created for you with some helpful, guiding comments.&lt;/p&gt;

&lt;p&gt;I have changed the &lt;strong&gt;workflow file name&lt;/strong&gt; to &lt;code&gt;build-and-publish.yml&lt;/code&gt; and the &lt;strong&gt;name&lt;/strong&gt; (&lt;em&gt;line 3&lt;/em&gt;) to, &lt;code&gt;Build and Publish&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;You have the on section where you can set triggers for various GitHub events.&lt;/p&gt;

&lt;p&gt;They are fairly self-explanatory, but if you’re struggling, there is some decent documentation on GitHub about about it here: &lt;a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions" rel="noopener noreferrer"&gt;GitHub Actions - Workflow Syntax&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For now, let’s remove all the comments as it is slightly more verbose for my liking, but please make sure you do understand what commands we are utilising. If not, things will become more clear as we edit this workflow now.&lt;/p&gt;

&lt;p&gt;If we remove all the comments and cut out some of the default template code, you should be left with something like this, fairly barebones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Publish&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above is telling us that when a push, or a pull request event happens on the main branch, execute this workflow, which when then run the jobs, and within jobs there are steps.&lt;/p&gt;

&lt;p&gt;Let’s focus on the steps and add the checkout of the git repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout the repo&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The uses is key to understand, it’s an action, and you can even have a look at the code in the &lt;a href="https://github.com/marketplace/actions/checkout" rel="noopener noreferrer"&gt;GitHub Marketplace&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’ll find useful documentation on what it does and how to use it, and you can even create your own GitHub Action to do whatever you want - and guess what? They too can be based on Docker containers!&lt;/p&gt;

&lt;p&gt;For more information about how to create custom GitHub Actions and use them to your advantage, or you want to share yours with the rest of us, please visit, &lt;a href="https://docs.github.com/en/free-pro-team@latest/actions/creating-actions" rel="noopener noreferrer"&gt;GitHub - Creating Actions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next up is the Docker build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build docker image&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker build . -t ghcr.io/peterjking/samples-weatherforecast:${{ github.sha }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are simply running &lt;code&gt;docker build&lt;/code&gt;, our &lt;code&gt;Dockerfile&lt;/code&gt; is in the current directory, hence the period &lt;code&gt;.&lt;/code&gt; and we set the image tag to my &lt;strong&gt;GitHub Container Registry : github.sha&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Each GitHub Account has a Container Registry, AKA GitHub Packages, for more information please visit - &lt;a href="https://ghcr.io" rel="noopener noreferrer"&gt;https://ghcr.io&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You image tag must be &lt;strong&gt;lowercase&lt;/strong&gt; - if you have a GitHub username like mine where there is uppercase letters, you can either hardcode it here like I have for the moment, store this in a GitHub Secret or convert to lowercase dynamically.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Now we have built the Docker image, we now would like to push it to our GitHub Container Registry, for this, we need valid credentials. For something like this, we can use a &lt;strong&gt;&lt;em&gt;GitHub Access Token&lt;/em&gt;&lt;/strong&gt; and securely store it in our GitHub Secrets area of our repo. Let’s go set this up now.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Navigate&lt;/em&gt; to &lt;strong&gt;Settings&lt;/strong&gt; (on your profile)&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcm1og1eu8x3k55t63el3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcm1og1eu8x3k55t63el3.png" alt="GitHub - Settings"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;&lt;em&gt;Select&lt;/em&gt; &lt;strong&gt;Developer Settings&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fp7xywty6ien749vxwii4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fp7xywty6ien749vxwii4.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;&lt;em&gt;Select&lt;/em&gt; &lt;strong&gt;Personal Access Tokens&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fychg6klmwi3c093xnaud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fychg6klmwi3c093xnaud.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;&lt;em&gt;Click&lt;/em&gt; &lt;strong&gt;Generate new token&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F423m6s5wm46dr1hiwdc2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F423m6s5wm46dr1hiwdc2.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;You’ll see a new screen whereby you can set the name of the PAS (Personal Access Token) and set the OAuth scopes for it. In our case, we only need &lt;strong&gt;write:packages&lt;/strong&gt;, &lt;strong&gt;read:packages&lt;/strong&gt; and &lt;strong&gt;delete:packages&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs43b81xvyjqeq4wxbxbe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs43b81xvyjqeq4wxbxbe.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Give the access token a name, I’ve called mine, &lt;code&gt;ghcr&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Copy&lt;/em&gt; your access token.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Once you’ve generated the token, you &lt;strong&gt;won’t&lt;/strong&gt; be able to get the value again, however, you can always regenerate a new value.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Navigate&lt;/em&gt; to your GitHub repository &lt;strong&gt;Settings&lt;/strong&gt; → &lt;strong&gt;Secrets&lt;/strong&gt; → &lt;em&gt;Click&lt;/em&gt; &lt;strong&gt;New repository secret&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz0nq3pf5b3l3nnjs8ysa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz0nq3pf5b3l3nnjs8ysa.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Enter a &lt;em&gt;name&lt;/em&gt;, &lt;em&gt;paste&lt;/em&gt; in your value, and &lt;em&gt;click&lt;/em&gt; → &lt;strong&gt;Add secret&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0ycifoua0180ciu1hacd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0ycifoua0180ciu1hacd.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Now we have successfully stored our Personal Access Token as a secret, we can use that to access our GitHub Container Registry and push our image.&lt;/p&gt;

&lt;p&gt;Below we use a GitHub Action called &lt;code&gt;login-action@v1&lt;/code&gt; and set it with some variables.&lt;/p&gt;

&lt;p&gt;For more information about this action, please visit, &lt;a href="https://github.com/marketplace/actions/docker-login" rel="noopener noreferrer"&gt;GitHub Actions Marketplace - docker-login&lt;/a&gt;&lt;/p&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to GitHub Container Registry&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.repository_owner }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GH_CR }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You’ll notice we are using an another internal GitHub environment variable called &lt;code&gt;repository_owner&lt;/code&gt;, this contains the repository owner, which equates to my username, and therefore, my GitHub Container Registry.&lt;/p&gt;

&lt;p&gt;Next there is the secret we created earlier, &lt;code&gt;GH_CR&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now we have login action setup, we can finally do our &lt;code&gt;docker push&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Push docker image&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker push ghcr.io/peterjking/samples-weatherforecast:${{ github.sha }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;That’s it!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;However, we can make a minor enhancement given we are using the image tag in two places.&lt;/p&gt;

&lt;p&gt;I’ve added an &lt;code&gt;env&lt;/code&gt; section which enables variables within my workflow, I’ve called it, &lt;code&gt;image-name&lt;/code&gt;, and used it in the &lt;code&gt;docker build&lt;/code&gt; and &lt;code&gt;docker push&lt;/code&gt; commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Full Dockerfile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Publish&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/peterjking/samples-weatherforecast:${{ github.sha }}&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-secure-push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout the repo&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build docker image&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker build . -t ${{ env.image-name }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to GitHub Container Registry&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.repository_owner }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GH_CR }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Push docker image&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker push ${{ env.image-name }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Make sure you workflow looks like the above, except the &lt;strong&gt;username&lt;/strong&gt; in the &lt;code&gt;env&lt;/code&gt; &lt;code&gt;image-name&lt;/code&gt; variable of course; this should be your &lt;strong&gt;username&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now you’re ready to &lt;em&gt;press&lt;/em&gt; → &lt;strong&gt;Start commit&lt;/strong&gt;!&lt;/p&gt;

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




&lt;p&gt;Once the GitHub Action kicks into &lt;strong&gt;&lt;em&gt;Action&lt;/em&gt;&lt;/strong&gt; - You’ll see it queued and then move into in progress.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The time delay between &lt;em&gt;Queued&lt;/em&gt; to &lt;em&gt;In Progress&lt;/em&gt;, if you’re used to an older style build system where you can be waiting minutes or even hours in some cases, you’ll be happy to learn this is &lt;strong&gt;&lt;em&gt;ultrafast&lt;/em&gt;&lt;/strong&gt;!&lt;/p&gt;
&lt;/blockquote&gt;

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




&lt;p&gt;The build has now started and &lt;strong&gt;In progress&lt;/strong&gt;.&lt;/p&gt;

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




&lt;p&gt;&lt;strong&gt;&lt;em&gt;All green... It worked!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

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




&lt;p&gt;Below we can now see the summary, the build has taken &lt;strong&gt;1 minute and 31 seconds&lt;/strong&gt;.&lt;/p&gt;

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




&lt;p&gt;By &lt;em&gt;clicking&lt;/em&gt; ‘&lt;strong&gt;build-secure-push&lt;/strong&gt;’ you will see the steps and their logs.&lt;/p&gt;

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




&lt;p&gt;Now, let’s check that our Docker image has actually been stored in our GitHub Container Registry by navigating to Packages.&lt;/p&gt;

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




&lt;p&gt;&lt;em&gt;Click&lt;/em&gt; the package link.&lt;/p&gt;

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




&lt;p&gt;You can see a helpful section at the top where there is a sample commandline. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Copy this now as we'll need it in the very next step to test&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

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




&lt;h2&gt;
  
  
  Let’s test this image
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;docker pull ghcr.io/peterjking/samples-weatherforecast-part-3:e5f0710a08eae6d19c39a8ef04dbddff211dcd88`
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; your image name will have a unique &lt;strong&gt;SHA1&lt;/strong&gt; as the Docker &lt;strong&gt;tag&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Copy the docker pull from your package, head-on over to the command line and execute.&lt;/p&gt;

&lt;p&gt;If you are working on a &lt;strong&gt;private&lt;/strong&gt; package, you will most certainly receive &lt;code&gt;unauthorized&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One way to solve this is to open your package to the &lt;strong&gt;public&lt;/strong&gt; by changing the &lt;strong&gt;visibility settings&lt;/strong&gt; in the &lt;strong&gt;Package Settings&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Once you make the package &lt;strong&gt;public&lt;/strong&gt;, you &lt;strong&gt;cannot go back&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The alternative is to login to your GitHub Container Registry first with docker login.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For now, I’ve simply made the package &lt;strong&gt;public&lt;/strong&gt;, for more information on docker login, please see, &lt;a href="https://docs.docker.com/engine/reference/commandline/login/" rel="noopener noreferrer"&gt;Docker Docs - Commandline - Login&lt;/a&gt;&lt;/p&gt;

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




&lt;h3&gt;
  
  
  Docker run
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;docker run -it --rm -p 8080:8080 ghcr.io/peterjking/samples-weatherforecast-part3:e5f0710a08eae6d19c39a8ef04dbddff211dcd88`
&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/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuq3lf2rctdzulfwz8wj9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuq3lf2rctdzulfwz8wj9.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Send a request
&lt;/h3&gt;

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




&lt;h2&gt;
  
  
  What have we learned?
&lt;/h2&gt;

&lt;p&gt;Well, we’ve learned a bunch here haven’t we? We have learned the very basics of &lt;strong&gt;GitHub Actions&lt;/strong&gt;, how to write your own &lt;strong&gt;workflow&lt;/strong&gt;, pretty much from scratch, generate a &lt;strong&gt;Personal Access Token&lt;/strong&gt; and store that token in your repository &lt;strong&gt;Secrets&lt;/strong&gt; area.&lt;/p&gt;

&lt;p&gt;In our workflow we added the docker build, logging into the &lt;strong&gt;GitHub Container registry&lt;/strong&gt;, and finally the docker push command.&lt;/p&gt;

&lt;p&gt;We’ve seen GitHub Actions in &lt;strong&gt;&lt;em&gt;Action!&lt;/em&gt;&lt;/strong&gt; The workflow summary and the workflow job output too.&lt;/p&gt;

&lt;p&gt;Finally, we’ve pulled down our Docker image from our public GitHub Container Registry and tested it locally. There is different compared to how we’ve done it before in this blog series, previously we’ve built the docker image locally and it’s already in our local Docker repository. We are now using the same flow as the base images you’ve used in our Dockerfile, and what other companies / individuals have stored in Docker Hub. Equally, you do not have to use the &lt;a href="https://github.com/features/packages" rel="noopener noreferrer"&gt;GitHub Container Registry&lt;/a&gt;, you could simply sign-up to &lt;a href="https://hub.docker.com/" rel="noopener noreferrer"&gt;Docker Hub&lt;/a&gt;. Not to forget there are other 3rd party solutions such as: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://azure.microsoft.com/en-gb/services/container-registry/" rel="noopener noreferrer"&gt;Azure Container Registry&lt;/a&gt; AKA - ACR.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/container-registry" rel="noopener noreferrer"&gt;Google Container Registry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/ecr/" rel="noopener noreferrer"&gt;Amazon Container Registry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mirantis.com/software/docker/image-registry/" rel="noopener noreferrer"&gt;Mirantis Container Registry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://goharbor.io/" rel="noopener noreferrer"&gt;Harbor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://quay.io/" rel="noopener noreferrer"&gt;Quay.io&lt;/a&gt; - from Red Hat.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.scaleway.com/en/container-registry/" rel="noopener noreferrer"&gt;Scaleway&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...and the list goes on!&lt;/p&gt;




&lt;h2&gt;
  
  
  Up next
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/peteking/api-s-from-dev-to-production-part-4-49g8"&gt;Part 4&lt;/a&gt; in this series will be about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add container scanning to our GitHub Action workflow&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Resolve security vulnerabilities&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  More information
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.azure.com" rel="noopener noreferrer"&gt;https://www.azure.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/" rel="noopener noreferrer"&gt;https://dotnet.microsoft.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.github.com" rel="noopener noreferrer"&gt;https://www.github.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com" rel="noopener noreferrer"&gt;https://www.docker.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com" rel="noopener noreferrer"&gt;https://www.pulumi.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>docker</category>
      <category>csharp</category>
      <category>github</category>
    </item>
  </channel>
</rss>
