<?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: lykins</title>
    <description>The latest articles on DEV Community by lykins (@lykins).</description>
    <link>https://dev.to/lykins</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%2F1172492%2F8f2e4ff2-2f40-4869-8ef5-029e3cc43bb0.jpeg</url>
      <title>DEV Community: lykins</title>
      <link>https://dev.to/lykins</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lykins"/>
    <language>en</language>
    <item>
      <title>First Look Nomad 1.11.x - System Job Deployments</title>
      <dc:creator>lykins</dc:creator>
      <pubDate>Wed, 14 Jan 2026 15:56:22 +0000</pubDate>
      <link>https://dev.to/lykins/first-look-nomad-111x-system-job-deployments-2131</link>
      <guid>https://dev.to/lykins/first-look-nomad-111x-system-job-deployments-2131</guid>
      <description>&lt;p&gt;Originally posted : &lt;a href="https://blog.lykins.xyz/posts/system-job-deployments/" rel="noopener noreferrer"&gt;https://blog.lykins.xyz/posts/system-job-deployments/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;This last &lt;a href="https://developer.hashicorp.com/nomad/docs/release-notes/nomad/v1-11-x" rel="noopener noreferrer"&gt;Nomad 1.11.x&lt;/a&gt; feature I will be covering is not new to Nomad itself, putting a new twist to how you update and manage system jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's New?
&lt;/h2&gt;

&lt;p&gt;In Nomad 1.11.x, system jobs now support &lt;a href="https://developer.hashicorp.com/nomad/docs/release-notes/nomad/v1-11-x#system-job-deployments" rel="noopener noreferrer"&gt;deployment functionality&lt;/a&gt; which will allow operators to manage changes and rollbacks for system jobs more effectively. This enhancement gives additional control over how system jobs are deployed and maintained across your infrastructure. &lt;/p&gt;

&lt;p&gt;As always, I would heavily recommend reading the release notes, which I have linked above, and the documentation on the &lt;a href="https://developer.hashicorp.com/nomad/docs/job-specification/update" rel="noopener noreferrer"&gt;update block&lt;/a&gt; as it has been updated to call out different behaviors when used with system jobs, such as the following: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In system jobs, the canary setting indicates the percentage of feasible nodes to which Nomad makes destructive allocation updates. System jobs do not support more than one allocation per node, so effectively setting canary to a positive integer means this percentage of feasible nodes gets a new version of the job if the update is destructive. Non-destructive updates ignore the canary field. Setting canary to 100 updates the job on all nodes. Percentage of nodes is always rounded up to the nearest integer. If canary is set, nodes that register during a deployment do not receive placements until after the deployment is promoted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since this also updates the Nomad UI, I will include screenshots as well. &lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing 1.11.x to Previous Versions
&lt;/h2&gt;

&lt;p&gt;So to start off, let's do a system job run comparison to see how system jobs behaved in previous versions of Nomad versus how they behave in 1.11.x. &lt;/p&gt;

&lt;p&gt;I will have one cluster which is running &lt;code&gt;1.10.5&lt;/code&gt; and another running &lt;code&gt;1.11.1&lt;/code&gt;. Both clusters are mostly identically set up - 1 server and 3 clients each. &lt;/p&gt;

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

&lt;p&gt;Running a very simple system job that deploys a busybox container on all clients in the cluster. Here is the jobspec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="s2"&gt;"system-deployment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;datacenters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dc1"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"system"&lt;/span&gt;

  &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;canary&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
    &lt;span class="nx"&gt;max_parallel&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nx"&gt;min_healthy_time&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"30s"&lt;/span&gt;
    &lt;span class="nx"&gt;healthy_deadline&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"5m"&lt;/span&gt;
    &lt;span class="nx"&gt;auto_promote&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="nx"&gt;auto_revert&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="s2"&gt;"test-group"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="s2"&gt;"test-task"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;driver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"docker"&lt;/span&gt;

      &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"busybox:1.36"&lt;/span&gt;
        &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sh"&lt;/span&gt;
        &lt;span class="nx"&gt;args&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"echo 'Running...'; sleep 3600"&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pay a bit more attention to the &lt;code&gt;update&lt;/code&gt; block and see how it is set up for this demo. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;canary&lt;/code&gt; is set to &lt;code&gt;30&lt;/code&gt;, when we do a destructive update, it will update 30% of the nodes first (1 of 3 nodes in this case) before promoting the version to the remaining nodes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;max_parallel&lt;/code&gt; is set to &lt;code&gt;1&lt;/code&gt;, only one node will be updated at a time. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;min_healthy_time&lt;/code&gt; is set to &lt;code&gt;30s&lt;/code&gt;, the updated allocation must be healthy for at least 30 seconds before proceeding.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;healthy_deadline&lt;/code&gt; is set to &lt;code&gt;5m&lt;/code&gt;, if the updated allocation does not become healthy within 5 minutes, the deployment will be considered failed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;auto_promote&lt;/code&gt; is set to &lt;code&gt;false&lt;/code&gt;, the deployment will not automatically proceed to update the remaining nodes after the canary node is healthy.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;auto_revert&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt;, if the deployment fails, it will automatically revert to the previous version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Canary is not necessary to trigger a deployment, but provides &lt;/p&gt;

&lt;h3&gt;
  
  
  UI
&lt;/h3&gt;

&lt;p&gt;On the left side of the screen is my homelab running &lt;code&gt;1.11.1&lt;/code&gt; and on the right side is a quick lab I spun up on multipass with &lt;code&gt;1.10.5&lt;/code&gt;. See if you can spot the subtle differences in the UI for system jobs between the two versions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Faqmblgav2drqbnd64ukp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Faqmblgav2drqbnd64ukp.png" alt=" " width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;1.11.1&lt;/code&gt; UI on the left, you can see the "Deployments" tab exists now for the system job, whereas in &lt;code&gt;1.10.5&lt;/code&gt; on the right, there is no such tab. With that, you will have additional deployment information and status on the overview page. &lt;/p&gt;

&lt;h3&gt;
  
  
  Update Behavior
&lt;/h3&gt;

&lt;p&gt;Now let's look at the update behavior between the two versions.&lt;/p&gt;

&lt;h4&gt;
  
  
  Running an Updated Job
&lt;/h4&gt;

&lt;p&gt;For this, I will update the container image version from &lt;code&gt;1.36&lt;/code&gt; to &lt;code&gt;1.37&lt;/code&gt; and run the update in the UI. Since this is a destructive update, you will see how each version handles it differently.: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F75uktr3jwh4rfuxgifv1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F75uktr3jwh4rfuxgifv1.png" alt=" " width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nothing different or major to see here, so we will carry along. &lt;/p&gt;

&lt;h4&gt;
  
  
  Deployment Behaviors
&lt;/h4&gt;

&lt;p&gt;After running the update:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fz30pspu4gqzw9ct9e1v6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fz30pspu4gqzw9ct9e1v6.png" alt=" " width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the left side with &lt;code&gt;1.11.x&lt;/code&gt;, you can see the deployment is in progress, and it is updating one of the three nodes (50% as per the canary setting). &lt;/p&gt;

&lt;p&gt;On the right side with &lt;code&gt;1.10.5&lt;/code&gt;, a deployment is not created, and the job is being updated per the &lt;code&gt;max_parallel&lt;/code&gt; setting. &lt;/p&gt;

&lt;h4&gt;
  
  
  Promotion
&lt;/h4&gt;

&lt;p&gt;You can see now with 1.11.x, the deployment is in progress, and it will wait till it is healthy based on any health checks - in this case it is only healthy allocations. Once healthy, it will require manual promotion to continue the deployment to the remaining nodes as &lt;code&gt;auto_promote&lt;/code&gt; is set to false as shown below: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fotu5dyat9yc6nmte5c00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fotu5dyat9yc6nmte5c00.png" alt=" " width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here I have the option, to promote or revert the deployment. If this was a real job, I could do some additional testing or validation, but for this demo, I will go ahead and promote the deployment and Nomad will handle updating the remaining allocations. &lt;/p&gt;

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

&lt;p&gt;System job deployments in Nomad 1.11.x bring a new level of control and reliability to managing critical system services across your infrastructure. By leveraging deployments, manual or controlled promotions, and automatic rollbacks, operators can ensure that updates to system jobs are handled safely and efficiently. &lt;/p&gt;

</description>
      <category>nomad</category>
    </item>
    <item>
      <title>First Look Nomad 1.11.x - Secret Block</title>
      <dc:creator>lykins</dc:creator>
      <pubDate>Fri, 19 Dec 2025 17:20:21 +0000</pubDate>
      <link>https://dev.to/lykins/first-look-nomad-111x-secret-block-4pdd</link>
      <guid>https://dev.to/lykins/first-look-nomad-111x-secret-block-4pdd</guid>
      <description>&lt;p&gt;Originally posted: &lt;a href="https://blog.lykins.xyz/posts/secret-block/" rel="noopener noreferrer"&gt;https://blog.lykins.xyz/posts/secret-block/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;The next Nomad &lt;a href="https://developer.hashicorp.com/nomad/docs/release-notes/nomad/v1-11-x" rel="noopener noreferrer"&gt;1.11.x&lt;/a&gt; feature we will be looking at is &lt;a href="https://developer.hashicorp.com/nomad/docs/release-notes/nomad/v1-11-x#artifact-secrets" rel="noopener noreferrer"&gt;Artifact Secrets&lt;/a&gt; which introduces a new &lt;code&gt;secret&lt;/code&gt; &lt;a href="https://developer.hashicorp.com/nomad/docs/job-specification/secret" rel="noopener noreferrer"&gt;block in the job specification&lt;/a&gt; to simplify the process of fetching secrets for your Nomad jobs in addition to one key difference compared to the existing &lt;code&gt;template&lt;/code&gt; block. &lt;/p&gt;

&lt;p&gt;Out of the box there are two &lt;a href="https://developer.hashicorp.com/nomad/docs/job-specification/secret#built-in-providers" rel="noopener noreferrer"&gt;built-in plugins&lt;/a&gt; which support Nomad variables and Vault. In addition to the built-in plugins there is a &lt;a href="https://developer.hashicorp.com/nomad/plugins/author/secret-provider" rel="noopener noreferrer"&gt;secret provider plugin framework&lt;/a&gt; which allows community or privately built plugins to support their secrets management systems. For example, check out the demo they provided for &lt;a href="https://developer.hashicorp.com/nomad/plugins/author/secret-provider#plugin-code" rel="noopener noreferrer"&gt;AWS Secrets Manager&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Note on my Setup
&lt;/h2&gt;

&lt;p&gt;For this post, portions of it I will be using Vault to manage my secrets. If you do not have Vault set up and would like to spin up a development instance to test with, check out the &lt;a href="https://developer.hashicorp.com/nomad/docs/secure/workload-identity/vault" rel="noopener noreferrer"&gt;workload identity tutorial&lt;/a&gt; for a quick start. &lt;/p&gt;

&lt;h2&gt;
  
  
  Before Diving In
&lt;/h2&gt;

&lt;p&gt;I want to make it clear that the new &lt;code&gt;secret&lt;/code&gt; block is not a 1 to 1 replacement for the existing &lt;code&gt;template&lt;/code&gt; block. They both have their own use cases and could potentially be used together in the same job specification. &lt;/p&gt;

&lt;p&gt;The most notable feature with the &lt;code&gt;secret&lt;/code&gt; block is that it can be interpreted in your job specification, which the &lt;code&gt;template&lt;/code&gt; block could not do. I'll get into more detail on that after this. &lt;/p&gt;

&lt;p&gt;When it comes to grabbing secrets to be used in your workloads, the &lt;code&gt;secret&lt;/code&gt; block is best suited for single execution workloads such as &lt;code&gt;batch&lt;/code&gt;, &lt;code&gt;dispatched&lt;/code&gt;, or &lt;code&gt;periodic&lt;/code&gt; jobs that need secrets at start time. For longer running workloads that may need to dynamically refresh secrets or other configuration, the &lt;code&gt;template&lt;/code&gt; block would still be the recommended as it can more dynamically manage your workloads if there is a change. &lt;/p&gt;

&lt;p&gt;For example, if you were to fetch a TLS certificate from Vault using the &lt;code&gt;template&lt;/code&gt; block and you want the task to restart when the certificate is renewed, you would do something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOH&lt;/span&gt;&lt;span class="sh"&gt;
{{ with pkiCert "pki/issue/foo" "common_name=foo.service.consul" "ip_sans=127.0.0.1" "format=pem" }}
{{ .Cert }}
{{ .CA }}
{{ .Key }}{{ end }}
&lt;/span&gt;&lt;span class="no"&gt;EOH
&lt;/span&gt;  &lt;span class="nx"&gt;destination&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${NOMAD_SECRETS_DIR}/bundle.pem"&lt;/span&gt;
  &lt;span class="nx"&gt;change_mode&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"restart"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When and if there is a change, the task will be restarted to pick up the new certificate. This is not something that can be accomplished with the &lt;code&gt;secret&lt;/code&gt; block as it is only fetched at start time and cannot trigger restarts on changes.&lt;/p&gt;

&lt;p&gt;With that out of the way, lets dive into the new &lt;code&gt;secret&lt;/code&gt; block and see how it works. &lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes the Secret Block Special?
&lt;/h2&gt;

&lt;p&gt;The main advantage of using the &lt;code&gt;secret&lt;/code&gt; block is that it can be interpreted in your job specification. This means you can directly reference secrets in your job specification or prestaged configuration in order to authenticate to pull images or artifacts.&lt;/p&gt;

&lt;p&gt;To show what this looks like, let's start with a very simple image I built and stored privately in GitHub's container registry and see how Nomad currently acts when attempting to pull without authentication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="s2"&gt;"secret-image"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;datacenters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="s2"&gt;"http"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nx"&gt;network&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="s2"&gt;"http"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8090&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="s2"&gt;"http"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;driver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"docker"&lt;/span&gt;
      &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;image&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ghcr.io/benjamin-lykins/nomad-secret-block-demo:latest"&lt;/span&gt;
        &lt;span class="nx"&gt;ports&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"http"&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We get this pleasant message in the UI: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Flvzlnp2va4uyvt1vkcfx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Flvzlnp2va4uyvt1vkcfx.png" alt="error" width="800" height="46"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So it looks like we cannot get the image due to authentication issues and this is where the &lt;code&gt;secret&lt;/code&gt; block comes into play.&lt;/p&gt;

&lt;p&gt;Historically, authentication for the Docker task driver was handled within the &lt;code&gt;auth&lt;/code&gt; &lt;a href="https://developer.hashicorp.com/nomad/docs/job-declare/task-driver/docker#authentication" rel="noopener noreferrer"&gt;block in the job's task specification&lt;/a&gt;. Which could require some configuration to be staged prior to running the job.&lt;/p&gt;

&lt;p&gt;Now we can do it this way: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make sure you have a valid GitHub personal access token (PAT) with the &lt;code&gt;read:packages&lt;/code&gt; scope.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F5hrncowueh1jrydvdijq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F5hrncowueh1jrydvdijq.png" alt="pat" width="800" height="72"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We will keep it easy and store it as a Nomad variable at &lt;code&gt;nomad/jobs&lt;/code&gt; - you might want more narrow scoping in your environments, but for testing this is simple enough - with the key &lt;code&gt;github_token&lt;/code&gt;. I will also include my GitHub username as a variable at &lt;code&gt;nomad/jobs&lt;/code&gt; with the key &lt;code&gt;github_user&lt;/code&gt; to easily reference it in the job specification. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F8lj3dlm0wrzd7gql59jb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8lj3dlm0wrzd7gql59jb.png" alt="nomad variables" width="800" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update the job specification to use the new &lt;code&gt;secret&lt;/code&gt; block to fetch the token from Vault and use it in the &lt;code&gt;auth&lt;/code&gt; block for the Docker driver.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="s2"&gt;"http"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;driver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"docker"&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;image&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ghcr.io/benjamin-lykins/nomad-secret-block-demo:latest"&lt;/span&gt;
      &lt;span class="nx"&gt;ports&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

      &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${secret.github.github_user}"&lt;/span&gt;
        &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${secret.github.github_token}"&lt;/span&gt;
        &lt;span class="nx"&gt;server_address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ghcr.io"&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="nx"&gt;secret&lt;/span&gt; &lt;span class="s2"&gt;"github"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"nomad"&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"nomad/jobs/"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two key takeaways here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;secret&lt;/code&gt; block was added which defines where to fetch the secrets from. In this case, we are using the Nomad variable provider to fetch both the username and token from &lt;code&gt;nomad/jobs/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Updated my task configuration to reference both the username and password in the &lt;code&gt;auth&lt;/code&gt; block using &lt;code&gt;${secret.github.github_user}&lt;/code&gt; and &lt;code&gt;${secret.github.github_token}&lt;/code&gt; syntax.
&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Running the job now successfully pulls the image from GitHub's container registry. Voilà - up and running!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdwjynhjxy5uggwsw1lq1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdwjynhjxy5uggwsw1lq1.png" alt="running job" width="800" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This provides a much simpler way to manage authentication for pulling images or artifacts in your Nomad jobs.&lt;/p&gt;

&lt;p&gt;And although I have only tested this with Docker, it should also simplify authentication for other task drivers that support downloading artifacts and images. &lt;/p&gt;

&lt;h2&gt;
  
  
  Diving a Bit Deeper
&lt;/h2&gt;

&lt;p&gt;I wanted to play around a bit more and see how the new &lt;code&gt;secret&lt;/code&gt; block compares to the existing &lt;code&gt;template&lt;/code&gt; block for fetching secrets and using them within your workloads. Say if you have a secret stored in Vault which you want to use in your task as an environment variable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fetching Secrets with the Template Block
&lt;/h3&gt;

&lt;p&gt;Previously, the only option was to use was the &lt;code&gt;template&lt;/code&gt; &lt;a href="https://developer.hashicorp.com/nomad/docs/job-specification/template" rel="noopener noreferrer"&gt;block in your job specification&lt;/a&gt; to render secrets or configuration from Vault, Nomad, Consul, or local files. This is built with go templating and uses Consul Template under the hood. This was a powerful and flexible way to manage secrets, but it did come with some complexity. Additionally, you are limited to placing this in the &lt;code&gt;task&lt;/code&gt; block. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;job -&amp;gt; group -&amp;gt; task -&amp;gt; template&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is an example of using the &lt;code&gt;template&lt;/code&gt; block to fetch a secret from Vault and inject it into the task's environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="s2"&gt;"template-block-demo"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"service"&lt;/span&gt;

  &lt;span class="nx"&gt;datacenters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dc1"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="s2"&gt;"template-block-demo"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="s2"&gt;"template-block-demo"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;driver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"exec"&lt;/span&gt;
      &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;destination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"secrets/file.env"&lt;/span&gt;
        &lt;span class="nx"&gt;env&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
SECRET = "{{ with secret "kv/data/default/template-block-demo" }}{{ .Data.data.secret }}{{ end }}"
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;vault&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

      &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/bin/sh"&lt;/span&gt;
        &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"while true; do echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Secret $SECRET&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;; sleep 5; done"&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verifying this works as expected, we can see the secret being printed out by the task:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fgez1eyspb6qoi00tcjjs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fgez1eyspb6qoi00tcjjs.png" alt="template-block-demo" width="800" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So you can see the template block is working and pulling the secret from key &lt;code&gt;secret&lt;/code&gt; with a value of &lt;code&gt;Agent Man&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fetching Secrets with the Secret Block
&lt;/h3&gt;

&lt;p&gt;With Nomad 1.11.x you can now define secrets directly by using the new &lt;code&gt;secret&lt;/code&gt; &lt;a href="https://developer.hashicorp.com/nomad/docs/job-specification/secret" rel="noopener noreferrer"&gt;block in your job specification&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;This block can also be used in more levels than just the &lt;code&gt;task&lt;/code&gt; block. So if you had a common token or secret to pull images or artifacts, you could define it at the &lt;code&gt;job&lt;/code&gt; or &lt;code&gt;group&lt;/code&gt; level and reference it in multiple tasks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;job -&amp;gt; secret \&lt;br&gt;
job -&amp;gt; group -&amp;gt; secret \&lt;br&gt;
job -&amp;gt; group -&amp;gt; task -&amp;gt; secret&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Recreating the previous example using the new &lt;code&gt;secret&lt;/code&gt; block. I will define the secret at the &lt;code&gt;job&lt;/code&gt; level this time, moving it from the &lt;code&gt;task&lt;/code&gt; block, so it can be reused across multiple tasks if needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;  &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="s2"&gt;"my_secret"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vault"&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"kv/data/default/secret-block-demo"&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;engine&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"kv_v2"&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 reference the secret directly into the task's &lt;code&gt;config&lt;/code&gt; using &lt;code&gt;${secret.my_secret.secret}&lt;/code&gt; syntax versus templating and injecting it in as an environment variable.&lt;/p&gt;

&lt;p&gt;Which will take the task config from this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;      &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;destination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"secrets/file.env"&lt;/span&gt;
        &lt;span class="nx"&gt;env&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
SECRET = "{{ with secret "kv/data/default/template-block-demo" }}{{ .Data.data.secret }}{{ end }}"
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/bin/sh"&lt;/span&gt;
        &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"while true; do echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Secret $SECRET&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;; sleep 5; done"&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;To know being able to reference it directly like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;  &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"while true; do echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Secret ${secret.my_secret.secret}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;; sleep 5; done"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full job specification below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="s2"&gt;"secret-block-demo"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"service"&lt;/span&gt;

  &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="s2"&gt;"my_secret"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vault"&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"kv/data/default/secret-block-demo"&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;engine&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"kv_v2"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;datacenters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dc1"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="s2"&gt;"secret-block-demo"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="s2"&gt;"secret-block-demo"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;driver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"exec"&lt;/span&gt;

      &lt;span class="nx"&gt;vault&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

      &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/bin/sh"&lt;/span&gt;
        &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"while true; do echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Secret ${secret.my_secret.secret}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;; sleep 5; done"&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verifying this works as expected, we can see the same secret being printed out by the task: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fyq4zinjjbgisf1agurf1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fyq4zinjjbgisf1agurf1.png" alt="running job secret block" width="800" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pulling Multiple Secrets with the Secret Block
&lt;/h3&gt;

&lt;p&gt;You can also use multiple secret blocks in a single job specification. In this example, I am pulling one secret from &lt;code&gt;kv/data/default/multi-secret-block-demo&lt;/code&gt; and another from &lt;code&gt;kv/data/global-secrets&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;My workload identity and role will allow any secret to be pulled from &lt;code&gt;global-secrets&lt;/code&gt;, and then any job specific secrets can be pulled from their respective paths dynamically. &lt;/p&gt;

&lt;p&gt;In this set up, I define both secrets at the &lt;code&gt;job&lt;/code&gt; level. &lt;/p&gt;

&lt;p&gt;Then I reference them in the task using &lt;code&gt;${secret.job_secret.job}&lt;/code&gt; and &lt;code&gt;${secret.global_secret.global}&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="s2"&gt;"multi-secret-block-demo"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"service"&lt;/span&gt;
  &lt;span class="nx"&gt;datacenters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dc1"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="s2"&gt;"job_secret"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vault"&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"kv/data/default/multi-secret-block-demo"&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;engine&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"kv_v2"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="s2"&gt;"global_secret"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vault"&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"kv/data/global-secrets"&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;engine&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"kv_v2"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="s2"&gt;"multi-secret-block-demo"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="s2"&gt;"multi-secret-block-demo"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;driver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"exec"&lt;/span&gt;

      &lt;span class="nx"&gt;vault&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

      &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/bin/sh"&lt;/span&gt;
        &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"while true; do echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Job: ${secret.job_secret.job}, Global: ${secret.global_secret.global}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;; sleep 5; done"&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verifying this works as expected, we can see both secrets being printed out by the task:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F2kxvmsvfxr6r0dy5lnzy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F2kxvmsvfxr6r0dy5lnzy.png" alt="multiple secrets" width="800" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As shown in the output, both secrets are successfully retrieved and displayed. &lt;/p&gt;

&lt;h3&gt;
  
  
  Using the Secret Block with Env Block
&lt;/h3&gt;

&lt;p&gt;In my previous two examples, I directly referenced the secret in the task's &lt;code&gt;config&lt;/code&gt; block. This last demo I want to show is using the &lt;code&gt;secret&lt;/code&gt; block in conjunction with the task's &lt;code&gt;env&lt;/code&gt; &lt;a href="https://developer.hashicorp.com/nomad/docs/job-specification/env" rel="noopener noreferrer"&gt;block&lt;/a&gt; to set environment variables for the task. So if you have a workload which checks for the existence of environment variables for authentication or configuration, you can use this method to set them.&lt;/p&gt;

&lt;p&gt;For this let's say you have a simple, but repetitive task of uploading processed files from our private datacenter to an S3 bucket that has been running as a cron job on a server for years. The server, appropriately named "DO-NOT-DELETE", has served you well, but it is time to send it on its way to retirement and migrate this workload to Nomad.&lt;/p&gt;

&lt;p&gt;So to keep this simple so any future administrator or engineer can understand, we will leverage the AWS cli to upload files to S3. Which we will need to provide our AWS credentials to the task as environment variables.&lt;/p&gt;

&lt;p&gt;Here is how we can accomplish this using the &lt;code&gt;secret&lt;/code&gt; block to fetch our AWS credentials and set them as environment variables in the task's &lt;code&gt;env&lt;/code&gt; block.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Store your AWS credentials in your secrets manager. In my case, I will be using Nomad variables with the path &lt;code&gt;nomad/jobs/s3-uploader&lt;/code&gt; and my necessary AWS environment variables. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a Nomad job specification that uses the &lt;code&gt;secret&lt;/code&gt; block to fetch the AWS credentials and set them as environment variables in the task's &lt;code&gt;env&lt;/code&gt; block. I'm going to use &lt;code&gt;raw_exec&lt;/code&gt; as the driver to keep it simple and just run a shell command to upload files to S3. The AWS cli is already installed on my Nomad client.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="s2"&gt;"s3-uploader"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"batch"&lt;/span&gt;
  &lt;span class="nx"&gt;datacenters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dc1"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="s2"&gt;"aws_credentials"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"nomad"&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"nomad/jobs/s3-uploader"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;periodic&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;crons&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"@hourly"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;prohibit_overlap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="s2"&gt;"s3-uploader"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="s2"&gt;"s3-uploader"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;driver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"raw_exec"&lt;/span&gt;

      &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${secret.aws_credentials.AWS_ACCESS_KEY_ID}"&lt;/span&gt;
        &lt;span class="nx"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${secret.aws_credentials.AWS_SECRET_ACCESS_KEY}"&lt;/span&gt;
        &lt;span class="nx"&gt;AWS_DEFAULT_REGION&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${secret.aws_credentials.AWS_DEFAULT_REGION}"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/bin/sh"&lt;/span&gt;
        &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"aws s3 sync /data/processed s3://the-bucket-i-will-use-for-this-nomad-demo/"&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this, I want to focus how I am specifically using the &lt;code&gt;secret&lt;/code&gt; block to fetch my AWS credentials from Nomad variables at the path &lt;code&gt;nomad/jobs/s3-uploader&lt;/code&gt;. Then in the task's &lt;code&gt;env&lt;/code&gt; block, I am setting the necessary AWS environment variables by referencing the secrets using &lt;code&gt;${secret.aws_credentials.&amp;lt;KEY&amp;gt;}&lt;/code&gt; syntax. With that set, I should be able to submit this job and at execution time, it will pull the latest AWS credentials and use them to authenticate with S3.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I'm too impatient so I will launch the job manually instead of waiting for the periodic schedule and verify it works as expected.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;upload: /data/processed/1 to s3://the-bucket-i-will-use-for-this-nomad-demo/1
Completed 1 file&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; with 2 file&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; remaining
upload: /data/processed/3 to s3://the-bucket-i-will-use-for-this-nomad-demo/3
Completed 2 file&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; with 1 file&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; remaining
upload: /data/processed/2 to s3://the-bucket-i-will-use-for-this-nomad-demo/2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could make this a bit more robust and use a &lt;a href="https://developer.hashicorp.com/nomad/docs/job-specification/lifecycle" rel="noopener noreferrer"&gt;poststop&lt;/a&gt; task to clean up the folder after the upload, but for time being this works and it would keep your files in sync. &lt;/p&gt;

&lt;p&gt;Before I finish writing a book, I will stop here and hope that you find this guide useful for understanding how to use the new &lt;code&gt;secret&lt;/code&gt; block in Nomad 1.11.x to fetch secrets for your workloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  In Summary
&lt;/h2&gt;

&lt;p&gt;For use cases when you need to fetch secrets for authenticating to pull images or artifacts, the new &lt;code&gt;secret&lt;/code&gt; block in Nomad 1.11.x simplifies the process by allowing direct references in your job specification, while also providing an alternative to using the &lt;code&gt;template&lt;/code&gt; block when appropriate.&lt;/p&gt;

&lt;p&gt;When needing to pass a secret into your workloads, both the &lt;code&gt;template&lt;/code&gt; and &lt;code&gt;secret&lt;/code&gt; blocks have their own use cases and can be used together if needed: &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Template Block&lt;/th&gt;
&lt;th&gt;Secret Block&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Location&lt;/td&gt;
&lt;td&gt;task block only&lt;/td&gt;
&lt;td&gt;job, group, or task block&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Syntax&lt;/td&gt;
&lt;td&gt;templating&lt;/td&gt;
&lt;td&gt;direct reference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reusability&lt;/td&gt;
&lt;td&gt;limited to task level&lt;/td&gt;
&lt;td&gt;dependent on placement in the job&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ease of Use&lt;/td&gt;
&lt;td&gt;more robust, with a tradeoff on complexity&lt;/td&gt;
&lt;td&gt;simpler and more straightforward&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secret Providers&lt;/td&gt;
&lt;td&gt;Vault, Nomad, Consul, local files&lt;/td&gt;
&lt;td&gt;built-in Vault and Nomad, extensible via plugins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use Cases&lt;/td&gt;
&lt;td&gt;complex templating needs&lt;/td&gt;
&lt;td&gt;simple secret retrieval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workloads&lt;/td&gt;
&lt;td&gt;longer running or dynamic workloads&lt;/td&gt;
&lt;td&gt;single execution, batch, dispatched, periodic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>nomad</category>
    </item>
    <item>
      <title>First Look Nomad 1.11.x - Client Node Introduction and Identity</title>
      <dc:creator>lykins</dc:creator>
      <pubDate>Thu, 18 Dec 2025 17:39:44 +0000</pubDate>
      <link>https://dev.to/lykins/first-look-nomad-111x-client-node-introduction-and-identity-32je</link>
      <guid>https://dev.to/lykins/first-look-nomad-111x-client-node-introduction-and-identity-32je</guid>
      <description>&lt;p&gt;Orginally posted here : &lt;a href="https://blog.lykins.xyz/posts/nomad-client-introduction/" rel="noopener noreferrer"&gt;https://blog.lykins.xyz/posts/nomad-client-introduction/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Nomad &lt;a href="https://developer.hashicorp.com/nomad/docs/release-notes/nomad/v1-11-x" rel="noopener noreferrer"&gt;1.11.x&lt;/a&gt; was recently released and I wanted to highlight some of the new features in a series of posts. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client node introduction and identity&lt;/li&gt;
&lt;li&gt;Artifact secrets&lt;/li&gt;
&lt;li&gt;System job deployments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this first post, I want to focus on the &lt;a href="https://developer.hashicorp.com/nomad/docs/release-notes/nomad/v1-11-x#client-node-introduction-and-identity" rel="noopener noreferrer"&gt;client node introduction and identity.&lt;/a&gt; This will require reading through the release note documentation, updated pages, CLI and API references, as a lot has been added around this feature and I will not cover in detail. &lt;/p&gt;

&lt;h2&gt;
  
  
  Client Node Introduction
&lt;/h2&gt;

&lt;p&gt;This feature adds in an additional layer of security to prevent unauthorized client from joining a Nomad cluster. Prior to this the first measure of security was valid TLS certificates between the client and server. With this new feature, when strictly enforced, clients must have a valid token to join the cluster.&lt;/p&gt;

&lt;p&gt;Think of this as multifactor authentication, but for your Nomad clusters. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Networking - can the client reach the server&lt;/li&gt;
&lt;li&gt;TLS - does the client have valid certificates for the cluster&lt;/li&gt;
&lt;li&gt;Client Introduction Token - does the client have a valid token to join the cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although I do not cover it in detail, this gives an added benefit in additional control over misconfigured clients trying to join the cluster. You can specify node names, node pools, and TTLs for the tokens you generate. &lt;/p&gt;

&lt;h2&gt;
  
  
  Nomad Version
&lt;/h2&gt;

&lt;p&gt;This feature requires 1.11.x on both the client and server side to function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nomad &lt;span class="nt"&gt;-v&lt;/span&gt;
Nomad v1.11.0
BuildDate 2025-11-11T16:18:19Z
Revision 9103d938133311b2da905858801f0e111a2df0a1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Server Configuration
&lt;/h2&gt;

&lt;p&gt;On the server side, a new configuration block has been added to the server configuration. This block is called &lt;code&gt;client_introduction&lt;/code&gt; with &lt;a href="https://developer.hashicorp.com/nomad/docs/v1.11.x/configuration/server#client_introduction-parameters" rel="noopener noreferrer"&gt;configurable options.&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;On my setup, it is going to be very simple as this is a lab environment - so a bit of precaution it might not be up to production standards. &lt;/p&gt;

&lt;p&gt;I set my enforcement to &lt;code&gt;strict&lt;/code&gt; so that any client that does not have a valid token will be rejected from joining the cluster, instead of a warning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data_dir&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/opt/nomad/"&lt;/span&gt;

&lt;span class="nx"&gt;acl&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;enabled&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;bootstrap_expect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nx"&gt;client_introduction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;enforcement&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"strict"&lt;/span&gt; &lt;span class="c1"&gt;#Default = "warn"&lt;/span&gt;
    &lt;span class="nx"&gt;default_identity_ttl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"5m"&lt;/span&gt;     &lt;span class="c1"&gt;#Default = "5m"&lt;/span&gt;
    &lt;span class="nx"&gt;max_identity_ttl&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"30m"&lt;/span&gt;    &lt;span class="c1"&gt;#Default = "30m"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;tls&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;rpc&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;ca_file&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/opt/nomad/tls/nomad-agent-ca.pem"&lt;/span&gt;
  &lt;span class="nx"&gt;cert_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/opt/nomad/tls/global-server-nomad.pem"&lt;/span&gt;
  &lt;span class="nx"&gt;key_file&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/opt/nomad/tls/global-server-nomad-key.pem"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Starting up my Nomad server with this configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; Starting Nomad agent...
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; Nomad agent configuration:

       Advertise Addrs: HTTP: 192.168.39.125:4646&lt;span class="p"&gt;;&lt;/span&gt; RPC: 192.168.39.125:4647&lt;span class="p"&gt;;&lt;/span&gt; Serf: 192.168.39.125:4648
            Bind Addrs: HTTP: &lt;span class="o"&gt;[&lt;/span&gt;0.0.0.0:4646]&lt;span class="p"&gt;;&lt;/span&gt; RPC: 0.0.0.0:4647&lt;span class="p"&gt;;&lt;/span&gt; Serf: 0.0.0.0:4648
                Client: &lt;span class="nb"&gt;false
             &lt;/span&gt;Log Level: INFO
               Node Id: cfc63efc-0f2d-6e60-c27a-4124a9cf8137
                Region: global &lt;span class="o"&gt;(&lt;/span&gt;DC: dc1&lt;span class="o"&gt;)&lt;/span&gt;
                Server: &lt;span class="nb"&gt;true
               &lt;/span&gt;Version: 1.11.0

&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; Nomad agent started! Log data will stream &lt;span class="k"&gt;in &lt;/span&gt;below:
...
    2025-11-18T18:32:03.142Z &lt;span class="o"&gt;[&lt;/span&gt;INFO]  nomad.raft: entering leader state: &lt;span class="nv"&gt;leader&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Node at 192.168.39.125:4647 [Leader]"&lt;/span&gt;
    2025-11-18T18:32:03.144Z &lt;span class="o"&gt;[&lt;/span&gt;INFO]  nomad: cluster leadership acquired
    2025-11-18T18:32:03.162Z &lt;span class="o"&gt;[&lt;/span&gt;INFO]  nomad.core: established cluster &lt;span class="nb"&gt;id&lt;/span&gt;: &lt;span class="nv"&gt;cluster_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bcdaffcb-a79a-d57c-03e4-6cf32357dee5 &lt;span class="nv"&gt;create_time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1763490723160485342
    2025-11-18T18:32:03.162Z &lt;span class="o"&gt;[&lt;/span&gt;INFO]  nomad: &lt;span class="nb"&gt;eval &lt;/span&gt;broker status modified: &lt;span class="nv"&gt;paused&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false
    &lt;/span&gt;2025-11-18T18:32:03.162Z &lt;span class="o"&gt;[&lt;/span&gt;INFO]  nomad: blocked evals status modified: &lt;span class="nv"&gt;paused&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false
    &lt;/span&gt;2025-11-18T18:32:03.237Z &lt;span class="o"&gt;[&lt;/span&gt;INFO]  nomad.keyring: initialized keyring: &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9cd82611-3df2-1c73-d010-05be16d750e8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty standard at this point, nothing new outside of the new configuration block. After starting up I bootstrapped the ACL system and pulled the initial management token. &lt;/p&gt;

&lt;h2&gt;
  
  
  Client Configuration
&lt;/h2&gt;

&lt;p&gt;On the client side, config will stay the same, no additional configuration is needed.Lets set up the Nomad client configuration. Which we should be good to join since we have valid certificates. Once we have the client config set up, we can start the Nomad agent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data_dir&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/opt/nomad/"&lt;/span&gt;

 &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
   &lt;span class="nx"&gt;servers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"192.168.39.125"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;tls&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;rpc&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;ca_file&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/opt/nomad/tls/nomad-agent-ca.pem"&lt;/span&gt;
  &lt;span class="nx"&gt;cert_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/opt/nomad/tls/global-client-nomad.pem"&lt;/span&gt;
  &lt;span class="nx"&gt;key_file&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/opt/nomad/tls/global-client-nomad-key.pem"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Starting up the Nomad client agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; Loaded configuration from /etc/nomad.d/nomad.hcl
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; Starting Nomad agent...
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; Nomad agent configuration:

       Advertise Addrs: HTTP: 192.168.39.133:4646
            Bind Addrs: HTTP: &lt;span class="o"&gt;[&lt;/span&gt;0.0.0.0:4646]
                Client: &lt;span class="nb"&gt;true
             &lt;/span&gt;Log Level: INFO
                Region: global &lt;span class="o"&gt;(&lt;/span&gt;DC: dc1&lt;span class="o"&gt;)&lt;/span&gt;
                Server: &lt;span class="nb"&gt;false
               &lt;/span&gt;Version: 1.11.0

&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; Nomad agent started! Log data will stream &lt;span class="k"&gt;in &lt;/span&gt;below:
...
    2025-11-18T19:14:17.242Z &lt;span class="o"&gt;[&lt;/span&gt;INFO]  client: started client: &lt;span class="nv"&gt;node_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;01f47f9a-7281-c450-6db0-3220fdc016a0
    2025-11-18T19:14:17.250Z &lt;span class="o"&gt;[&lt;/span&gt;ERROR] client.rpc: error performing RPC to server: &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"rpc error: Permission denied"&lt;/span&gt; &lt;span class="nv"&gt;rpc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Node.Register &lt;span class="nv"&gt;server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.39.125:4647
    2025-11-18T19:14:17.251Z &lt;span class="o"&gt;[&lt;/span&gt;ERROR] client.rpc: error performing RPC to server which is not safe to automatically retry: &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"rpc error: Permission denied"&lt;/span&gt; &lt;span class="nv"&gt;rpc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Node.Register &lt;span class="nv"&gt;server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.39.125:4647
    2025-11-18T19:14:17.253Z &lt;span class="o"&gt;[&lt;/span&gt;ERROR] client: error registering: &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"rpc error: Permission denied"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hmm. That did not seem to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Client Join Failures
&lt;/h2&gt;

&lt;p&gt;Before we dive into how to get the client to join, as platform owners we should be aware of when client nodes are failing to join, so lets go to the server logs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2025-11-18T19:14:24.952Z &lt;span class="o"&gt;[&lt;/span&gt;ERROR] nomad.client: node registration without introduction token: &lt;span class="nv"&gt;enforcement_level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;strict &lt;span class="nv"&gt;node_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;01f47f9a-7281-c450-6db0-3220fdc016a0 &lt;span class="nv"&gt;node_pool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default &lt;span class="nv"&gt;node_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From what we can see here, a node attempted to register without an introduction token. If the enforcement was &lt;code&gt;warn&lt;/code&gt;, it would join, but it would log as a warning. Since we set it to &lt;code&gt;strict&lt;/code&gt;, it is rejected from joining the cluster. For production enviornments, I would recommend after upgrading to 1.11.x to set enforcement to &lt;code&gt;warn&lt;/code&gt; first, monitor logs for any unauthorized clients trying to join, and then once you have a repeatble workflow in place, switch to &lt;code&gt;strict&lt;/code&gt; enforcement.&lt;/p&gt;

&lt;p&gt;Also, if you have telemetry enabled, as you probably should, there are &lt;a href="https://developer.hashicorp.com/nomad/docs/v1.11.x/monitor#client-introduction" rel="noopener noreferrer"&gt;new metrics around client introductions&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  How to Generate Client Introduction Tokens
&lt;/h2&gt;

&lt;p&gt;Before we generate our client introduction token, I just want to cover what I have set up for this lab environment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Got a server&lt;/li&gt;
&lt;li&gt;Got a client&lt;/li&gt;
&lt;li&gt;Both running 1.11.x&lt;/li&gt;
&lt;li&gt;Both with valid TLS certificates - not necessary for client introductions but I guess I've gotten use to always having TLS enabled.&lt;/li&gt;
&lt;li&gt;ACL Enabled - please just always make this a habit on all clusters. 

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;node:write&lt;/code&gt; policy is required to generate tokens.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Server has &lt;code&gt;client_introduction&lt;/code&gt; block configured. &lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;So our next step is to generate a client introduction token. This can be done via the CLI or API. I will show both ways.&lt;/p&gt;

&lt;p&gt;To do this I'm going to walk thorugh creating a policy, role, and then generating a token from that role. We will use that token to request a client introduction token when starting up the Nomad client agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create ACL Policy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# client-introduction.hcl&lt;/span&gt;
&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"write"&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 shell"&gt;&lt;code&gt;nomad acl policy apply client-introduction client-introduction.hcl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create ACL Role from Policy
&lt;/h3&gt;

&lt;p&gt;This is optional as you can generate tokens directly from policies, but I use roles to help manage my policies better.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nomad acl role create &lt;span class="nt"&gt;--tls-skip-verify&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"client-introduction"&lt;/span&gt; &lt;span class="nt"&gt;-policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"client-introduction"&lt;/span&gt;
ID           &lt;span class="o"&gt;=&lt;/span&gt; cf0b4a43-b00f-cc30-b656-b34d66151b04
Name         &lt;span class="o"&gt;=&lt;/span&gt; client-introduction
Description  &lt;span class="o"&gt;=&lt;/span&gt; &amp;lt;none&amp;gt;
Policies     &lt;span class="o"&gt;=&lt;/span&gt; client-introduction
Create Index &lt;span class="o"&gt;=&lt;/span&gt; 117
Modify Index &lt;span class="o"&gt;=&lt;/span&gt; 117
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generate Nomad Token via CLI
&lt;/h3&gt;

&lt;p&gt;Now we can generate a Nomad token from the role we just created. This token will be used to request client introduction tokens. Ecosystem wise I would recommend &lt;a href="https://developer.hashicorp.com/nomad/docs/secure/acl/tokens/vault" rel="noopener noreferrer"&gt;Vault to generate and manage these tokens&lt;/a&gt;, but for this lab we will just generate it via the CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@server:~# nomad acl token create &lt;span class="nt"&gt;-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"client-intro-token-1"&lt;/span&gt; &lt;span class="nt"&gt;-role-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"client-introduction"&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client &lt;span class="nt"&gt;-ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8h
Accessor ID  &lt;span class="o"&gt;=&lt;/span&gt; 8c22a7c0-44f5-044d-ef84-bfa06118faf4
Secret ID    &lt;span class="o"&gt;=&lt;/span&gt; d99d678d-426c-330e-74f3-de53a868e2f9
Name         &lt;span class="o"&gt;=&lt;/span&gt; client-intro-token-1
Type         &lt;span class="o"&gt;=&lt;/span&gt; client
Global       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false
&lt;/span&gt;Create Time  &lt;span class="o"&gt;=&lt;/span&gt; 2025-11-18 21:01:44.62075624 +0000 UTC
Expiry Time  &lt;span class="o"&gt;=&lt;/span&gt; 2025-11-19 05:01:44.62075624 +0000 UTC
Create Index &lt;span class="o"&gt;=&lt;/span&gt; 128
Modify Index &lt;span class="o"&gt;=&lt;/span&gt; 128
Policies     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[]&lt;/span&gt;

Roles
ID                                    Name
cf0b4a43-b00f-cc30-b656-b34d66151b04  client-introduction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generate Nomad Client Introduction Token via API
&lt;/h3&gt;

&lt;p&gt;This feature introduces a new &lt;a href="https://developer.hashicorp.com/nomad/api-docs/acl/identities#create-client-introduction-identity" rel="noopener noreferrer"&gt;API endpoint&lt;/a&gt; to generate client introduction tokens. &lt;/p&gt;

&lt;p&gt;Optional: You can create a JSON file to hold the request body, but in this case we are not any additional parameters so we can just send an empty JSON object.&lt;/p&gt;

&lt;p&gt;Example payload file, just to show you can send additional parameters for added control such as &lt;code&gt;NodeName&lt;/code&gt;, &lt;code&gt;NodePool&lt;/code&gt;, and &lt;code&gt;TTL&lt;/code&gt;.:&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;"NodeName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node-338ef6e9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"NodePool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"TTL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"15m"&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;p&gt;Curl request to generate client introduction token - I am sending an empty JSON object as the body.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"X-Nomad-Token: d99d678d-426c-330e-74f3-de53a868e2f9"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    https://localhost:4646/v1/acl/identity/client-introduction-token &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; intro_token.jwt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&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;"JWT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJhbGciOiJSUzI1NiIsImtpZCI6IjljZDgyNjExLTNkZjItMWM3My1kMDEwLTA1YmUxNmQ3NTBlOCIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJub21hZHByb2plY3QuaW8iLCJleHAiOjE3NjM1MDA5MTgsImlhdCI6MTc2MzUwMDYxOCwianRpIjoiMzBkYjU0OGUtNzQ1OS05MGVlLWYwYmItMjk1MGIzNDJjMTQ2IiwibmJmIjoxNzYzNTAwNjE4LCJub21hZF9ub2RlX25hbWUiOiIiLCJub21hZF9ub2RlX3Bvb2wiOiJkZWZhdWx0Iiwic3ViIjoibm9kZS1pbnRyb2R1Y3Rpb246Z2xvYmFsOmRlZmF1bHQ6OmRlZmF1bHQifQ.acAuFxXshidUF_61l3HFgI0W_eW9ag5J9jptklNb5was2go5E8IHtvueWeYOMcsZakegLX8GNMy8CmBhtSRmmvzWxCoXf5CDHo9jqyxTrFc8yWFozbCucieTWBsyyKMqgkgf52QCm5owO0Kw40AzDK0sqkYrmzh4FrXCruomk0Zmmw9nHfactLRkT4PfYE_RpvMp7ptNkXNlEuEypZ-oVGxOK1maXZ51F0A9xrrMqxBroIfpusuGj8OI4j8VCjDnTKlI1wPWPvNVdirpt3vWp3FdZ-OCTPIGD7wmTzzhdec-cADWrQOxuczmJPoH0U4iF7AdNLswJGhhIKyN4ktVbA"&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;h3&gt;
  
  
  Generate Nomad Client Introduction Token via CLI
&lt;/h3&gt;

&lt;p&gt;In addition to generating the token via API, you can also generate it via the &lt;a href="https://developer.hashicorp.com/nomad/commands/v1.11.x/node/intro/create" rel="noopener noreferrer"&gt;CLI&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Similar to the API, you can pass in additional parameters if needed, but in this case we will just generate a basic token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nomad node intro create  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; intro_token.jwt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The content of the intro_token.jwt file will be similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="s2"&gt;"eyJhbGciOiJSUzI1NiIsImtpZCI6IjljZDgyNjExLTNkZjItMWM3My1kMDEwLTA1YmUxNmQ3NTBlOCIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJub21hZHByb2plY3QuaW8iLCJleHAiOjE3NjM1MDMwNzgsImlhdCI6MTc2MzUwMjc3OCwianRpIjoiZDlhZmRmZjEtMTcxYy1lYWJjLTU4MmEtZjc3NmJjNzY2N2I0IiwibmJmIjoxNzYzNTAyNzc4LCJub21hZF9ub2RlX25hbWUiOiIiLCJub21hZF9ub2RlX3Bvb2wiOiJkZWZhdWx0Iiwic3ViIjoibm9kZS1pbnRyb2R1Y3Rpb246Z2xvYmFsOmRlZmF1bHQ6OmRlZmF1bHQifQ.P9xDbK9dlD8g7XvZ8Wq87mxddCTniaHP0qsR5ovGw9H6KtG6-rHpkYYzKgHgQBVXDUnQjo29yUBLDbZFsa2woub4bX_Qv3LH1TqNq1eAJ3W3W0sHBBj_NZx9GKwW_5MmdlXWe9_tgQVpTM19x1f0Tu8qGRZsQAvZWWhMJZQr9Ad32OP492CT5r_8aZafYCyZfJJqr8puFBFe5_PsEQ5uL-BXT-L8owE41OV9kUZ8zUF3YG75hWCzIcZp-nZHuqzOzqIElXA83V-u4GObrOZKIltkazIbH-JZ20XTA83b8YSh477XQ7OtI1wBD_fKZOXb5ehl7gjFocheSN9LgSdHFg"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that, we are ready to use this token when starting up our Nomad client agent. In both of these we only want the value of the &lt;code&gt;JWT&lt;/code&gt; field and not the surrounding JSON structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Client Introduction Token
&lt;/h2&gt;

&lt;p&gt;To use the client introduction token, we have a &lt;a href="https://developer.hashicorp.com/nomad/commands/agent#client-intro-token" rel="noopener noreferrer"&gt;couple of options&lt;/a&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set the &lt;code&gt;-client-intro-token&lt;/code&gt; flag when starting up the Nomad agent.&lt;/li&gt;
&lt;li&gt;Place the token in a file named &lt;code&gt;intro_token.jwt&lt;/code&gt; within the client's state directory (default is &lt;code&gt;&amp;lt;data_dir&amp;gt;/&amp;lt;client_state_dir&amp;gt;/&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Set the &lt;code&gt;NOMAD_CLIENT_INTRO_TOKEN&lt;/code&gt; environment variable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this, I set the token with the environment variable and start up the Nomad client agent again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nomad agent &lt;span class="nt"&gt;-config&lt;/span&gt; /etc/nomad.d/nomad.hcl
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; Loaded configuration from /etc/nomad.d/nomad.hcl
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; Starting Nomad agent...
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; Nomad agent configuration:

       Advertise Addrs: HTTP: 192.168.39.133:4646
            Bind Addrs: HTTP: &lt;span class="o"&gt;[&lt;/span&gt;0.0.0.0:4646]
                Client: &lt;span class="nb"&gt;true
             &lt;/span&gt;Log Level: INFO
                Region: global &lt;span class="o"&gt;(&lt;/span&gt;DC: dc1&lt;span class="o"&gt;)&lt;/span&gt;
                Server: &lt;span class="nb"&gt;false
               &lt;/span&gt;Version: 1.11.0

&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; Nomad agent started! Log data will stream &lt;span class="k"&gt;in &lt;/span&gt;below:
 ...
    2025-11-19T02:38:56.724Z &lt;span class="o"&gt;[&lt;/span&gt;INFO]  client: setting node identity token
    2025-11-19T02:38:56.724Z &lt;span class="o"&gt;[&lt;/span&gt;INFO]  client: started client: &lt;span class="nv"&gt;node_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;01f47f9a-7281-c450-6db0-3220fdc016a0
    2025-11-19T02:38:56.728Z &lt;span class="o"&gt;[&lt;/span&gt;INFO]  client: setting node identity token
    2025-11-19T02:38:56.735Z &lt;span class="o"&gt;[&lt;/span&gt;INFO]  client: node registration &lt;span class="nb"&gt;complete
    &lt;/span&gt;2025-11-19T02:39:03.193Z &lt;span class="o"&gt;[&lt;/span&gt;INFO]  client: node registration &lt;span class="nb"&gt;complete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there we have it! The client has successfully joined the Nomad cluster using the client introduction token.&lt;/p&gt;

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

&lt;p&gt;The client node introduction feature in Nomad 1.11.x adds an important layer of security to your Nomad clusters. By requiring clients to present a valid introduction token, you can better control which nodes are allowed to join your cluster and node pools. This is especially useful in environments where security is a top priority.&lt;/p&gt;

</description>
      <category>nomad</category>
    </item>
    <item>
      <title>GitHub Actions Pipeline</title>
      <dc:creator>lykins</dc:creator>
      <pubDate>Tue, 19 Nov 2024 03:11:59 +0000</pubDate>
      <link>https://dev.to/lykins/github-actions-pipeline-4397</link>
      <guid>https://dev.to/lykins/github-actions-pipeline-4397</guid>
      <description>&lt;p&gt;At this stage, my setup is complete, and I’m ready to start building images. To automate the image factory, I connected two GitHub Actions workflows in a chain. While additional workflows could be introduced for more specialized images, these are general-purpose images with a basic setup, so a simple two-workflow approach suffices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflows&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Base Image&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The first workflow focuses on creating base images. These images are hardened according to enterprise policies or industry standards, such as NIST, PCI, or other relevant frameworks. In addition to hardening, I ensure that essential infrastructure or application monitoring tools are integrated into the image.&lt;/p&gt;

&lt;p&gt;For base images, I worked with the following operating systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Red Hat 9.3&lt;/li&gt;
&lt;li&gt;Ubuntu 24.04&lt;/li&gt;
&lt;li&gt;Windows Server 2022&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In terms of platforms, the workflow was configured to build images for AWS, Azure, and vSphere for my presentation.&lt;/p&gt;

&lt;p&gt;Once a base image was successfully built, its metadata was sent to their corresponding HCP Packer buckets. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;App Image&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's an updated and refined version of your section:&lt;/p&gt;

&lt;p&gt;If the base images are successfully built, the next stage focuses on creating app images. This step layers additional components onto the base image. For Linux images, this might involve setting up Docker or Podman to enable running containerized workloads. For Windows images, configuring IIS could be a suitable solution for hosting web applications.&lt;/p&gt;

&lt;p&gt;In my presentation, this is where I concluded the process. However, I did build a Nomad-specific image based on my Docker images to demonstrate the concept.&lt;/p&gt;

&lt;p&gt;In a real-world scenario, I would go further by adding additional layers to specialize the image. This approach simplifies the "last mile" provisioning and deployment, ensuring that images are fine-tuned for their specific use cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Credit&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There is an opportunity for an end user to select which image or platform to build on, if looking to do a one-off. Most of the process  originated from this repository : &lt;a href="https://github.com/tfo-apj-demos/packer-images" rel="noopener noreferrer"&gt;packer-images&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Inputs&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For both base and app workflows, the following is set up:&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;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;singleBuild&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choice&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;image override&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;rhel&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ubuntu&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;windows&lt;/span&gt;
      &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choice&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform override&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
          &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;vsphere&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;aws&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;azure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If triggering a workflow manually, it would look like this: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fx2pz0mt3zs245ajesnyf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fx2pz0mt3zs245ajesnyf.png" alt=" " width="640" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build Steps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first step runs on a GitHub Actions hosted runner and will output which images and platforms to build. &lt;/p&gt;

&lt;p&gt;If All images were selected, then RHEL, Ubuntu, and Windows images will be built in AWS, Azure, and vSphere. You can override which image to build or what platform to build on, in any mix.&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;selectimages&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="s2"&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;get-images&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get-images&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;if [[ "${{ inputs.singleBuild }}" != "all" ]] &amp;amp;&amp;amp; [[ -n "${{ github.event.inputs.singleBuild }}" ]] ; then&lt;/span&gt;
            &lt;span class="s"&gt;export IMAGES=$(echo ${{ inputs.singleBuild }} | jq -R '["\(.)"]')&lt;/span&gt;
            &lt;span class="s"&gt;echo "images_out"=$IMAGES""&lt;/span&gt;
            &lt;span class="s"&gt;echo "images_out"=$IMAGES"" &amp;gt;&amp;gt; $GITHUB_OUTPUT&lt;/span&gt;
          &lt;span class="s"&gt;else&lt;/span&gt;
            &lt;span class="s"&gt;export IMAGES=$(echo $DEFAULT_IMAGES | jq -R 'split(", ")')&lt;/span&gt;
            &lt;span class="s"&gt;echo "images_out"=$IMAGES""&lt;/span&gt;
            &lt;span class="s"&gt;echo "images_out"=$IMAGES"" &amp;gt;&amp;gt; $GITHUB_OUTPUT&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;

          &lt;span class="s"&gt;if [[ "${{ inputs.platform }}" != "all" ]] &amp;amp;&amp;amp; [[ -n "${{ github.event.inputs.platform }}" ]] ; then&lt;/span&gt;
            &lt;span class="s"&gt;export PLATFORMS=$(echo ${{ inputs.platform }} | jq -R '["\(.)"]')&lt;/span&gt;
            &lt;span class="s"&gt;echo "platforms_out"=$PLATFORMS""&lt;/span&gt;
            &lt;span class="s"&gt;echo "platforms_out"=$PLATFORMS"" &amp;gt;&amp;gt; $GITHUB_OUTPUT&lt;/span&gt;
          &lt;span class="s"&gt;else&lt;/span&gt;
            &lt;span class="s"&gt;export PLATFORMS=$(echo $DEFAULT_PLATFORMS | jq -R 'split(", ")')&lt;/span&gt;
            &lt;span class="s"&gt;echo "platforms_out"=$PLATFORMS""&lt;/span&gt;
            &lt;span class="s"&gt;echo "platforms_out"=$PLATFORMS"" &amp;gt;&amp;gt; $GITHUB_OUTPUT&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;
    &lt;span class="na"&gt;outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.get-images.outputs.images_out }}&lt;/span&gt;
      &lt;span class="na"&gt;platforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.get-images.outputs.platforms_out }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step takes outputs from the first step. I use a matrix strategy with image + platform as the combinations. &lt;/p&gt;

&lt;p&gt;I had never tried to do this before, but was surprised it worked &lt;code&gt;runs-on: ["${{matrix.platform}}"]&lt;/code&gt;. This will ensure my AWS builds will run on my privately networks on AWS, same with Azure and vSphere.&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;build&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="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${{matrix.platform}}"&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="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;selectimages&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;HCP_PACKER_BUCKET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{matrix.platform}}-${{matrix.image}}&lt;/span&gt;
      &lt;span class="na"&gt;PLATFORM&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{matrix.platform}}&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{fromJson(needs.selectimages.outputs.images)}}&lt;/span&gt;
        &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{fromJson(needs.selectimages.outputs.platforms)}}&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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;FraBle/clean-after-action@v1&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;Setup `packer`&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;hashicorp/setup-packer@main&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;setup&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;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&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 code&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@v4.1.1&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;Image Builder&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;packer init -force -var-file=${BUILDPATH}/${LAYER}/${{matrix.image}}/variables/example.pkrvars.hcl ${BUILDPATH}/${LAYER}/${{matrix.image}}/.&lt;/span&gt;

          &lt;span class="s"&gt;packer build -force -var-file=${BUILDPATH}/${LAYER}/${{matrix.image}}/variables/example.pkrvars.hcl ${BUILDPATH}/${LAYER}/${{matrix.image}}/.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Further Automation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's an updated and polished version of your section:&lt;/p&gt;

&lt;p&gt;As the process transitions towards production, image building can be triggered on a schedule. While monthly builds might have sufficed in the past, the frequency should ideally increase to weekly. This ensures that security patches and updates are consistently applied. Since the process is automated and runs in the background, this cadence is both practical and efficient.&lt;/p&gt;

&lt;p&gt;For testing my images, I’ve utilized the HCP Packer + Terraform integration, which allows me to automatically provision and validate my images. Although I haven’t fully implemented an automated testing pipeline yet, I’ve explored tools like &lt;a href="https://developer.hashicorp.com/packer/integrations/mondoohq/cnspec" rel="noopener noreferrer"&gt;cnspec&lt;/a&gt; by Mondoo. This tool integrates with Packer to scan images for compliance during the build process, offering a promising solution for ensuring quality and adherence to security standards.&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>packer</category>
    </item>
    <item>
      <title>Multipurpose Packer Templates</title>
      <dc:creator>lykins</dc:creator>
      <pubDate>Sat, 16 Nov 2024 04:04:39 +0000</pubDate>
      <link>https://dev.to/lykins/multipurpose-packer-templates-5322</link>
      <guid>https://dev.to/lykins/multipurpose-packer-templates-5322</guid>
      <description>&lt;p&gt;We've now gotten to a point that I have probably the most experience with, and that is with Packer. After Terraform, Packer was the next HashiCorp tool that I picked up. Thankfully, the syntax moved from JSON to HCL a couple of years ago at this point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is a Multipurpose Packer Templates&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Before I get into the weeds, making hyper-generalized, multipurpose templates might be overkill. Really, my goal here is to write a base template which will work with multiple clouds and on premise hosting solutions. This of it as a way to jumpstart your image building process.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Really what I would like to try to do here is to build just a few templates which can handle all my Packer builds. Think of it as a module in Terraform. A module should be reusable, dynamic, and flexible. &lt;/p&gt;

&lt;p&gt;My goal is two templates. &lt;/p&gt;

&lt;p&gt;One for base builds.&lt;/p&gt;

&lt;p&gt;One for app builds. &lt;/p&gt;

&lt;p&gt;Base builds will run from a source cloud image or iso, then register with HCP Packer. &lt;/p&gt;

&lt;p&gt;App builds will leverage HCP Packer to pick up the parent image and build upon it. &lt;/p&gt;

&lt;p&gt;Additionally, I will only want a single template that will run both Windows and Linux builds. The easiest way to do this would be Ansible, then you can use the &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The downside with Packer are some of its limitations, specifically with meta-arguments. &lt;/p&gt;

&lt;p&gt;As much as I would like to have a count on a provisioner, that is not an option. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workarounds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So there are a couple of workarounds. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dynamic&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.hashicorp.com/packer/docs/templates/hcl_templates/expressions#dynamic-blocks" rel="noopener noreferrer"&gt;Dynamic blocks&lt;/a&gt; are similar to Terraform, where you can run a &lt;code&gt;foreach&lt;/code&gt; against it, and it will loop through the input. &lt;/p&gt;

&lt;p&gt;Here is an example of a vsphere-iso source block where I loop a map variable to create add a disk to the image I am building. For example, if you want to have a disk just for the os and a disk for application data, you can now loop it in a dynamic block.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"vsphere_storage_config"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Configuration for vSphere storage."&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="nx"&gt;disk_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;
 &lt;span class="nx"&gt;disk_thin_provisioned&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bool&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;disk_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;
&lt;span class="nx"&gt;disk_thin_provisioned&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;disk_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20000&lt;/span&gt;
&lt;span class="nx"&gt;disk_thin_provisioned&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;The variable gets injected into here and the result if leaving the default, this would create 2 disks. If you need more or less, you can specify in the respective var-file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;  &lt;span class="nx"&gt;disk_controller_type&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vsphere_disk_controller_type&lt;/span&gt;
  &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="s2"&gt;"storage"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vsphere_storage_config&lt;/span&gt;
    &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;disk_size&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disk_size&lt;/span&gt;
      &lt;span class="nx"&gt;disk_thin_provisioned&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disk_thin_provisioned&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;Additionally, you can use dynamic where you can as a toggle. For example, if you are or are not using HCP Packer, you can enable it or not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;  &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="s2"&gt;"hcp_packer_registry"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hcp_packer_enabled&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;bucket_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vsphere_bucket_name&lt;/span&gt;
      &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vsphere_bucket_description&lt;/span&gt;
      &lt;span class="nx"&gt;bucket_labels&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vsphere_hcp_bucket_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"role"&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;
        &lt;span class="s2"&gt;"os"&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;
        &lt;span class="s2"&gt;"os_version"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;os_version&lt;/span&gt;
        &lt;span class="s2"&gt;"os_type"&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;os_type&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;build_labels&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vsphere_hcp_build_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"packer_version"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;packer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&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;Dynamic blocks are useful if they can be used, but for provisioners you will need to rely on other solutions. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Only&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So for my HashiConf talk, I decided to use Ansible, since it can run on both Windows and Linux, but if using powershell vs shell provisioners in the same build, then it can be tricky. &lt;/p&gt;

&lt;p&gt;This is the solution I came up with, and it has seemed to work so far.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;  &lt;span class="c1"&gt;# Probably not the best method to get this to work, but it works for now. &lt;/span&gt;
  &lt;span class="nx"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"shell"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;only&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;os_type&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"linux"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"vsphere-iso.this"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"foo.this"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;scripts&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build_shell_scripts&lt;/span&gt;
    &lt;span class="nx"&gt;environment_vars&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build_shell_script_environment_vars&lt;/span&gt;
    &lt;span class="nx"&gt;valid_exit_codes&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build_shell_script_exit_codes&lt;/span&gt;
    &lt;span class="nx"&gt;execute_command&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build_shell_script_execute_command&lt;/span&gt;
    &lt;span class="nx"&gt;expect_disconnect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build_shell_script_expect_disconnect&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"powershell"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;only&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;os_type&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"windows"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"vsphere.this"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"foo.this"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;scripts&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build_powershell_scripts&lt;/span&gt;
    &lt;span class="nx"&gt;environment_vars&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build_powershell_script_environment_vars&lt;/span&gt;
    &lt;span class="nx"&gt;use_pwsh&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build_powershell_script_use_pwsh&lt;/span&gt;
    &lt;span class="nx"&gt;valid_exit_codes&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build_powershell_script_exit_codes&lt;/span&gt;
    &lt;span class="nx"&gt;execute_command&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build_powershell_script_execute_command&lt;/span&gt;
    &lt;span class="nx"&gt;elevated_user&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;
    &lt;span class="nx"&gt;elevated_password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Password&lt;/span&gt;
    &lt;span class="nx"&gt;execution_policy&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build_powershell_script_execution_policy&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Additional Support&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are a couple of other ways I keep my templates generalized. &lt;/p&gt;

&lt;p&gt;Really the biggest thing is the structure and layout of my repository I build my images from. &lt;/p&gt;

&lt;p&gt;I have a Packer directory which has three subdirectories. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;builds - where my packer templates live - base and app. &lt;/li&gt;
&lt;li&gt;pkrvars - where my var-files live which make the templates plug and playable. &lt;/li&gt;
&lt;li&gt;shared - shared are anything which are can be used across multiple packer builds, no matter the source (aws, azure, gcp, vsphere) or the os type and distribution(linux or windows, rhel or debian). &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tree below is a work in progress, which I will later reference, but it is close to the final product.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;└── packer
    ├── builds
    │   ├── app
    │   │   ├── build.pkr.hcl
    │   │   ├── hcp.pkr.hcl
    │   │   ├── locals.pkr.hcl
    │   │   ├── plugins.pkr.hcl
    │   │   ├── source_aws.pkr.hcl
    │   │   ├── source_azure.pkr.hcl
    │   │   ├── source_gce.pkr.hcl
    │   │   ├── variables_aws.pkr.hcl
    │   │   ├── variables_azure.pkr.hcl
    │   │   ├── variables_common.pkr.hcl
    │   │   └── variables_hcp.pkr.hcl
    │   └── base
    │       ├── build.pkr.hcl
    │       ├── locals.pkr.hcl
    │       ├── plugins.pkr.hcl
    │       ├── source_aws.pkr.hcl
    │       ├── source_azure.pkr.hcl
    │       ├── source_gce.pkr.hcl
    │       ├── variables_aws.pkr.hcl
    │       ├── variables_azure.pkr.hcl
    │       ├── variables_common.pkr.hcl
    │       └── variables_hcp.pkr.hcl
    ├── pkrvars
    │   ├── os
    │   │   ├── linux
    │   │   │   ├── debian
    │   │   │   │   ├── base.pkrvars.hcl
    │   │   │   │   ├── nomad.pkrvars.hcl
    │   │   │   │   ├── packer.pkrvars.hcl
    │   │   │   │   ├── terraform.pkrvars.hcl
    │   │   │   │   └── vault.pkrvars.hcl
    │   │   │   └── rhel
    │   │   └── windows
    │   │       └── base.pkrvars.hcl
    │   └── sources
    │       └── sources.pkrvars.hcl
    └── shared
        ├── README.MD
        ├── bootstrap
        │   └── bootstrap_win.txt
        ├── files
        ├── playbooks
        │   └── readme.md
        └── scripts
            ├── debian
            │   ├── base.sh
            │   ├── deprovision-aws.sh
            │   ├── deprovision-azure.sh
            │   ├── deprovision-google.sh
            │   ├── docker.sh
            │   └── nomad.sh
            ├── rhel
            └── windows
                ├── base.ps1
                ├── deprovision-aws.ps1
                ├── deprovision-azure.ps1
                └── deprovision-google.ps1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Shared Resources&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When using shared scripts or files, I would recommend using &lt;code&gt;environment_vars&lt;/code&gt; on the provisioners and have them specific for the build or source platform you will build on. &lt;/p&gt;

&lt;p&gt;For files, use the &lt;a href="https://developer.hashicorp.com/packer/docs/templates/hcl_templates/functions/file/templatefile" rel="noopener noreferrer"&gt;templatefile&lt;/a&gt; function to make a more dynamic file based on the image. So instead of multiple userdata files or autounattend files, you use the &lt;code&gt;templatefile&lt;/code&gt; function to customize it. &lt;/p&gt;




&lt;p&gt;I am going to call it a night here. My little one is teething and deciding to keep us up at night, so I might try to get to bed. &lt;/p&gt;

&lt;p&gt;My newest Packer project is here: &lt;a href="https://github.com/benjamin-lykins/plug-and-play-packer" rel="noopener noreferrer"&gt;plug-and-play-packer&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;In this I have all three major clouds - AWS, Azure, GCP. I will also try to get vSphere in, as well. &lt;/p&gt;

&lt;p&gt;But more on that later, wife will get on me for being up late soon. &lt;/p&gt;

&lt;p&gt;cheers, lykins. &lt;/p&gt;

</description>
      <category>packer</category>
    </item>
    <item>
      <title>Containerizing GitHub Runner</title>
      <dc:creator>lykins</dc:creator>
      <pubDate>Sun, 10 Nov 2024 04:28:56 +0000</pubDate>
      <link>https://dev.to/lykins/containerizing-github-runner-blh</link>
      <guid>https://dev.to/lykins/containerizing-github-runner-blh</guid>
      <description>&lt;p&gt;So containerizing a custom runner image actually came out a bit simpler than I had expected, this is mostly since others did the heavy lifting for me :). &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Credit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All credit goes to the following posts:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://baccini-al.medium.com/how-to-containerize-a-github-actions-self-hosted-runner-5994cc08b9fb" rel="noopener noreferrer"&gt;How to containerize a GitHub Actions self-hosted runner&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://testdriven.io/blog/github-actions-docker/" rel="noopener noreferrer"&gt;Deploying Self-Hosted GitHub Actions Runners with Docker&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Changes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I did make a few changes when I was building my runners. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Built with Packer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gave me an opportunity to play with the &lt;a href="https://developer.hashicorp.com/packer/integrations/hashicorp/docker/latest/components/builder/docker" rel="noopener noreferrer"&gt;Packer Docker Plugin&lt;/a&gt;. &lt;/p&gt;

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

&lt;p&gt;Since I will be using Ansible Playbooks during my Packer builds, I added it into the image versus setting it up every time the pipeline runs. &lt;/p&gt;

&lt;p&gt;In addition to Ansible Playbooks, I add/update the following:&lt;/p&gt;

&lt;p&gt;Runners are more customizable and are ephemeral. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Labels: allow custom labels to be added when registering the runner. &lt;/li&gt;
&lt;li&gt;Ephemeral: runner will exit after it fails or completes a run. 

&lt;ul&gt;
&lt;li&gt;Nomad will bring a runner back up to replace. &lt;/li&gt;
&lt;li&gt;Runner would deregister itself from GitHub. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Configuration&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;./config.sh &lt;span class="nt"&gt;--url&lt;/span&gt; https://github.com/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REPOSITORY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RUNNER_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--labels&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RUNNER_LABELS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--token&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REG_TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--unattended&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--ephemeral&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Cleanup&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;cleanup&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Removing runner..."&lt;/span&gt;
    ./config.sh remove &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--unattended&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--token&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REG_TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s1"&gt;'cleanup'&lt;/span&gt; EXIT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Building the Runner&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you are not familiar with Dockerfiles, then you should be fine. I found this to be straightforward whether you have experience or if you are a beginner. I recommend checking out the Packer Docker plugin documentation. &lt;/p&gt;

&lt;p&gt;All the code and configuration can be found here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/benjamin-lykins/nomad-image-factory/tree/main/packer/builds/github-actions-images/builder" rel="noopener noreferrer"&gt;Runner Image&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Really the only major thing you'll need outside your GitHub account is a registry to drop it in. I used GitHub as well in this case. You can also build locally. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Step 1&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Add your plugins. I only used Docker, so that's it there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;packer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github.com/hashicorp/docker"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1"&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;&lt;em&gt;Step 2&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Like other Packer builds, configure your source block. &lt;br&gt;
Keeping it like GitHub Actions and sticking with Ubuntu. Most other distributions I found to work. &lt;/p&gt;

&lt;p&gt;I also specify the USER and script to run when it starts. The script will register the runner once the container is up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"docker"&lt;/span&gt; &lt;span class="s2"&gt;"actions-runner-builder"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu:latest"&lt;/span&gt;
  &lt;span class="nx"&gt;commit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;changes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"USER runner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"ENTRYPOINT [&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;/start.sh&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"LABEL runner_version=${var.runner_version}"&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;&lt;em&gt;Step 3&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This all goes pretty quickly. The next step is the build block. &lt;/p&gt;

&lt;p&gt;First provisioner is to drop the &lt;code&gt;start.sh&lt;/code&gt; file onto the image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;  &lt;span class="nx"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"file"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;source&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"packer/builds/github-actions-images/builder/scripts/start.sh"&lt;/span&gt;
    &lt;span class="nx"&gt;destination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/start.sh"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next provisioner will configure the runner.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;  &lt;span class="nx"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"shell"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;environment_vars&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"RUNNER_VERSION=${var.runner_version}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"DEBIAN_FRONTEND=noninteractive"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"packer/builds/github-actions-images/builder/scripts/install.sh"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For my custom image, I wanted to point out three packages. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;xorriso - for creating iso images (vSphere intended).&lt;/li&gt;
&lt;li&gt;unzip - used when setting up Packer in the workflows. &lt;/li&gt;
&lt;li&gt;git - used in packer builds to tag images and metadata. &lt;/li&gt;
&lt;li&gt;ansible - most of the builds I ran I used ansible playbooks. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The script itself...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash -e&lt;/span&gt;

&lt;span class="c"&gt;# This script sets up the environment for installing GitHub Actions runner.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Environment Variables:&lt;/span&gt;
&lt;span class="c"&gt;#   DEBIAN_FRONTEND - Set to 'noninteractive' to avoid interactive prompts during package installation.&lt;/span&gt;
&lt;span class="c"&gt;#   RUNNER_ARCH - Determines the architecture of the system using dpkg.&lt;/span&gt;
&lt;span class="c"&gt;#   RUNNER_VERSION - Specifies the version of the GitHub Actions runner to install. Defaults to 2.319.1 if not set.&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DEBIAN_FRONTEND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;noninteractive
&lt;span class="nv"&gt;RUNNER_ARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;RUNNER_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RUNNER_VERSION&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.319.1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# This script checks if the environment variable RUNNER_ARCH is set to "amd64".&lt;/span&gt;
&lt;span class="c"&gt;# If it is, the script changes the value of RUNNER_ARCH to "x64".&lt;/span&gt;
&lt;span class="c"&gt;# Finally, it prints the value of RUNNER_ARCH to the console.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RUNNER_ARCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"amd64"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;RUNNER_ARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"x64"&lt;/span&gt;
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"RUNNER_ARCH: &lt;/span&gt;&lt;span class="nv"&gt;$RUNNER_ARCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# This script determines the operating system of the runner by using the `uname -s` command,&lt;/span&gt;
&lt;span class="c"&gt;# converts the output to lowercase, and stores it in the RUNNER_OS variable.&lt;/span&gt;
&lt;span class="c"&gt;# It then prints the determined operating system.&lt;/span&gt;
&lt;span class="nv"&gt;RUNNER_OS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'[:upper:]'&lt;/span&gt; &lt;span class="s1"&gt;'[:lower:]'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"RUNNER_OS: &lt;/span&gt;&lt;span class="nv"&gt;$RUNNER_OS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# This script creates a new user named 'runner' with a home directory.&lt;/span&gt;
useradd &lt;span class="nt"&gt;-m&lt;/span&gt; runner

&lt;span class="c"&gt;# This script updates the package lists, upgrades installed packages,&lt;/span&gt;
&lt;span class="c"&gt;# and installs a set of essential development tools and libraries.&lt;/span&gt;
apt-get update &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get upgrade &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    curl &lt;span class="se"&gt;\&lt;/span&gt;
    jq &lt;span class="se"&gt;\&lt;/span&gt;
    build-essential &lt;span class="se"&gt;\&lt;/span&gt;
    libssl-dev &lt;span class="se"&gt;\&lt;/span&gt;
    libffi-dev &lt;span class="se"&gt;\&lt;/span&gt;
    python3 &lt;span class="se"&gt;\&lt;/span&gt;
    python3-venv &lt;span class="se"&gt;\&lt;/span&gt;
    python3-dev &lt;span class="se"&gt;\&lt;/span&gt;
    python3-pip &lt;span class="se"&gt;\&lt;/span&gt;
    unzip &lt;span class="se"&gt;\&lt;/span&gt;
    libicu-dev &lt;span class="se"&gt;\&lt;/span&gt;
    nodejs &lt;span class="se"&gt;\&lt;/span&gt;
    xorriso &lt;span class="se"&gt;\&lt;/span&gt;
    git

&lt;span class="c"&gt;# This installs Ansible.&lt;/span&gt;
apt-get update &lt;span class="nt"&gt;-y&lt;/span&gt;
apt-get &lt;span class="nb"&gt;install &lt;/span&gt;software-properties-common &lt;span class="nt"&gt;-y&lt;/span&gt;
add-apt-repository &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="nt"&gt;--update&lt;/span&gt; ppa:ansible/ansible
apt-get &lt;span class="nb"&gt;install &lt;/span&gt;ansible &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="c"&gt;# This script installs the GitHub Actions runner.&lt;/span&gt;
&lt;span class="c"&gt;# It performs the following steps:&lt;/span&gt;
&lt;span class="c"&gt;# 1. Changes the directory to /home/runner.&lt;/span&gt;
&lt;span class="c"&gt;# 2. Creates a new directory named actions-runner.&lt;/span&gt;
&lt;span class="c"&gt;# 3. Changes the directory to actions-runner.&lt;/span&gt;
&lt;span class="c"&gt;# 4. Downloads the specified version of the GitHub Actions runner tarball from GitHub.&lt;/span&gt;
&lt;span class="c"&gt;# 5. Extracts the contents of the downloaded tarball.&lt;/span&gt;
&lt;span class="c"&gt;# 6. Removes the downloaded tarball to clean up.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Environment variables required:&lt;/span&gt;
&lt;span class="c"&gt;# - RUNNER_VERSION: The version of the GitHub Actions runner to install.&lt;/span&gt;
&lt;span class="c"&gt;# - RUNNER_OS: The operating system of the runner (e.g., linux, osx).&lt;/span&gt;
&lt;span class="c"&gt;# - RUNNER_ARCH: The architecture of the runner (e.g., x64, arm64).&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /home/runner &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mkdir &lt;/span&gt;actions-runner &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;actions-runner &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  curl &lt;span class="nt"&gt;-O&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; https://github.com/actions/runner/releases/download/v&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RUNNER_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/actions-runner-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RUNNER_OS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RUNNER_ARCH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RUNNER_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.tar.gz &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="nb"&gt;tar &lt;/span&gt;xzf ./actions-runner-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RUNNER_OS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RUNNER_ARCH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RUNNER_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.tar.gz &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; actions-runner-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RUNNER_OS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RUNNER_ARCH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RUNNER_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.tar.gz

&lt;span class="c"&gt;# This script changes the ownership of the home directory of the 'runner' user,&lt;/span&gt;
&lt;span class="c"&gt;# installs dependencies required for GitHub Actions runner using the provided script,&lt;/span&gt;
&lt;span class="c"&gt;# and then cleans up the APT cache to free up space.&lt;/span&gt;
&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; runner ~runner &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  /home/runner/actions-runner/bin/installdependencies.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# This script changes the current directory to the root directory&lt;/span&gt;
&lt;span class="c"&gt;# and then grants execute permissions to the start.sh script located in the root directory.&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; / &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="nb"&gt;chmod&lt;/span&gt; +x ./start.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last step in the build is the tag and push the image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;  &lt;span class="nx"&gt;post-processors&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;post-processor&lt;/span&gt; &lt;span class="s2"&gt;"docker-tag"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;repository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.registry_server}/${var.registry_username}/actions-runner-builder"&lt;/span&gt;
      &lt;span class="nx"&gt;tags&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"latest"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;additional_tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;post-processor&lt;/span&gt; &lt;span class="s2"&gt;"docker-push"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;login&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="nx"&gt;login_username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registry_username&lt;/span&gt;
      &lt;span class="nx"&gt;login_password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registry_token&lt;/span&gt;
      &lt;span class="nx"&gt;login_server&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registry_server&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 is it :). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Faxroo5ohgd47phqjfzj2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Faxroo5ohgd47phqjfzj2.png" alt="runner-image" width="770" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running the Runner&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So a couple of variables need passed when running the container. Only two are required - REPO and TOKEN. &lt;/p&gt;

&lt;p&gt;REPO will specify the repository to add the runner to. &lt;br&gt;
TOKEN is your GitHub Personal Access Token. &lt;/p&gt;

&lt;p&gt;Additionally, NAME and LABELS can be added to better identify your runner or use in your Actions Workflows later on. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F3n15dh6n1dg5ta3xh4li.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F3n15dh6n1dg5ta3xh4li.png" alt="Running the Runner" width="516" height="728"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you run it you should see it register and connect...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ftokqzovynvz7ka9tqnz9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ftokqzovynvz7ka9tqnz9.png" alt="Registered" width="579" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you check in your repository you will now see the registered runner. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fh8j871yx71lhckmgutgc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fh8j871yx71lhckmgutgc.png" alt="gha" width="779" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stopping the Runner&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since the runners are ephemeral, once a workflow run is done in GitHub Actions, it would automatically exit and the process would deregister it. &lt;/p&gt;

&lt;p&gt;If running on Nomad or with Docker Compose, a new runner will spin up. In my case, I will stop it manually and since it is not orchestrated a new one should not spin up. &lt;/p&gt;

&lt;p&gt;Well... Guess I am using an unnecessary argument. Nevertheless, it triggers the runner to clean up after itself. Something I am trying to hope my three-year-old would do. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fs5rlneijry8lp93yiomm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fs5rlneijry8lp93yiomm.png" alt="stopping" width="800" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you check back your repository, the runner should now be removed cleanly. Not garbage or hung registrations left. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fen8mghkejws7f6d244hm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fen8mghkejws7f6d244hm.png" alt="clean" width="766" height="132"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is it. Simple. &lt;/p&gt;

</description>
      <category>nomad</category>
      <category>github</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Intermission: HashiConf</title>
      <dc:creator>lykins</dc:creator>
      <pubDate>Mon, 21 Oct 2024 20:47:27 +0000</pubDate>
      <link>https://dev.to/lykins/intermission-hashiconf-36o2</link>
      <guid>https://dev.to/lykins/intermission-hashiconf-36o2</guid>
      <description>&lt;p&gt;Just getting back from HashiConf. After 5ish years of watching it virtually, I was finally able to attend in person. This is also the first time I have also publicly presented, definitely will do it again if I am given the opportunity. &lt;/p&gt;

&lt;p&gt;This is me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fwd66qnk9yz7588w14l13.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fwd66qnk9yz7588w14l13.png" alt="me on stage" width="800" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes, I was wearing a fanny pack. I used it to carry my two of my props on stage. Raspberry Pi and one of my Minicomputers to show what I ran Nomad on. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F9eee4cn1ldx28zjcxqea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F9eee4cn1ldx28zjcxqea.png" alt="my fanny pack" width="800" height="603"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A big thank you to all who attended my talk in person. Hope it gave some interest to those to pick up and use Nomad. &lt;/p&gt;

</description>
      <category>nomad</category>
    </item>
    <item>
      <title>Setting up the Nomad Cluster</title>
      <dc:creator>lykins</dc:creator>
      <pubDate>Sun, 13 Oct 2024 03:25:36 +0000</pubDate>
      <link>https://dev.to/lykins/setting-up-the-nomad-cluster-9l6</link>
      <guid>https://dev.to/lykins/setting-up-the-nomad-cluster-9l6</guid>
      <description>&lt;p&gt;My first task in setting up my Nomad Image Factory is creating the Nomad cluster. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lab:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3 : Mini Computers

&lt;ul&gt;
&lt;li&gt;N100 CPU&lt;/li&gt;
&lt;li&gt;16 GB Memory&lt;/li&gt;
&lt;li&gt;2 : 1GB Nics&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Proxmox&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F8hpb2o2wiuqw6t88zjcf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8hpb2o2wiuqw6t88zjcf.png" alt="Cluster Nodes" width="800" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nomad Cluster:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple Cluster

&lt;ul&gt;
&lt;li&gt;3 Nodes with Server and Client Role&lt;/li&gt;
&lt;li&gt;2 CPU&lt;/li&gt;
&lt;li&gt;4 GB Memory&lt;/li&gt;
&lt;li&gt;Ubuntu 24&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fc2rpwcc1l3li0ka9ui8a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fc2rpwcc1l3li0ka9ui8a.png" alt="Nomad VMs" width="800" height="103"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installing Nomad:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I take the easy way to install Nomad.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;wget gpg coreutils

wget &lt;span class="nt"&gt;-O-&lt;/span&gt; https://apt.releases.hashicorp.com/gpg | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sudo &lt;/span&gt;gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/share/keyrings/hashicorp-archive-keyring.gpg

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;lsb_release &lt;span class="nt"&gt;-cs&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; main"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
| &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/hashicorp.list

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;nomad
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installing you should be able to check the Nomad version to confirm. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Frc7d4udgilgm1g9ximzd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Frc7d4udgilgm1g9ximzd.png" alt="Nomad Version" width="422" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Additional Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want to do a manual install and setup, I would recommend following this guide : &lt;a href="https://developer.hashicorp.com/nomad/tutorials/enterprise/production-deployment-guide-vm-with-consul#download-nomad" rel="noopener noreferrer"&gt;HERE :)&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;If sticking with using the package manager, just a few notes - atleast on he Ubuntu side. I would not think there is a difference with RHEL-based. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Service&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fd3zokudzekwenr36fvtf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fd3zokudzekwenr36fvtf.png" alt="systemctl" width="731" height="95"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is setup up with a default config. If this was a server only node, I would have set up a nomad user and group, and change root in both places nomad.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F0v275eqpth6zaa0ev1df.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F0v275eqpth6zaa0ev1df.png" alt="user" width="653" height="86"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Other note is the nomad environment and config file. They are set up in &lt;code&gt;/etc/nomad.d&lt;/code&gt;. If changing you'll need to update those values in the service config. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fikupza286gkdg7v2a184.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fikupza286gkdg7v2a184.png" alt="config" width="429" height="79"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Config&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My favorite thing about Nomad and what drew me to it was its use of HCL. My background prior to messing with Nomad is with Terraform + Packer, so it is a common syntax I am already familiar with. &lt;/p&gt;

&lt;p&gt;The common config location is &lt;code&gt;/etc/nomad.d&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fy700xo0qhixfwm5jvryo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fy700xo0qhixfwm5jvryo.png" alt="ls -l" width="289" height="44"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For now, I will not change anything in here. I probably will play with TLS down the road, but since this is a lab, I am sticking to the defaults.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ff1f1qqco1i9vd6mtejox.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ff1f1qqco1i9vd6mtejox.png" alt="nomad.hcl" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Starting up Nomad:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Last item I had to hit was install Docker. Went ahead and did that since I will be running containers on this. &lt;/p&gt;

&lt;p&gt;At this point I am ready to get it up and running...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F4m8zhtp2w51xw0har52w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F4m8zhtp2w51xw0har52w.png" alt="starting service" width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just to note, I entered in the IP address to bootstrap the servers. Everything else is pretty much the default...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data_dir&lt;/span&gt;  &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/opt/nomad/data"&lt;/span&gt;
&lt;span class="nx"&gt;bind_addr&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0"&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;enabled&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;bootstrap_expect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="nx"&gt;server_join&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;retry_join&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"192.168.39.109"&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="nx"&gt;client&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;servers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I got the service running on each I checked the UI to confirm all have been joined...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fw3x8er1bhh34cuv8mfav.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fw3x8er1bhh34cuv8mfav.png" alt="Joined Servers" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I did notice when checking the clients and their driver status that I must have missed the Docker engine on one of them. I'll jump back in and get that cleaned up. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fzd57taklbov4xrnlshhl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fzd57taklbov4xrnlshhl.png" alt="missing driver" width="800" height="192"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Well that is it for the night. It is getting late and HashiConf starts in two days :)&lt;/p&gt;

&lt;p&gt;Cheers, &lt;br&gt;
lykins&lt;/p&gt;

</description>
      <category>nomad</category>
    </item>
    <item>
      <title>Intro - Nomad Image Factory</title>
      <dc:creator>lykins</dc:creator>
      <pubDate>Wed, 09 Oct 2024 02:43:22 +0000</pubDate>
      <link>https://dev.to/lykins/nomad-image-factory-intro-eeo</link>
      <guid>https://dev.to/lykins/nomad-image-factory-intro-eeo</guid>
      <description>&lt;p&gt;Howdy. &lt;/p&gt;

&lt;p&gt;Just a quick introduction in preparation for my series to expand on my HashiConf talk. &lt;/p&gt;

&lt;p&gt;Unfortunately, I had to cut out portions of building a full end to end Nomad Image Factory from my talk, but I am going to use my blog to fill in what was missed. &lt;/p&gt;

&lt;p&gt;My series of posts are going to be as follows: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting up Nomad - Development Server and Cluster&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Preparing for my talk, I did not get the chance to play with Consul, so I am going to give it a shot, as well. &lt;/p&gt;

&lt;p&gt;My presentation covers running Nomad with the &lt;code&gt;-dev&lt;/code&gt; flag, as well as, bootstrapping a cluster. &lt;/p&gt;

&lt;p&gt;For my blog, I am going to use my own homelab to show how lightweight Nomad is. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fka2g98v9xouut17qi2p8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fka2g98v9xouut17qi2p8.png" alt="Homelab" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Homelab Features: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 - Simple Switch&lt;/li&gt;
&lt;li&gt;3 - Simple minicomputers&lt;/li&gt;
&lt;li&gt;1 - 13-year-old Lenovo laptop&lt;/li&gt;
&lt;li&gt;1 - A former Gaming PC&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think my wife is ready for me to move it somewhere else. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Containerizing GitHub Runner&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To be transparent, most of the heavy lifting was based on two blog posts. I updated mine to be ephemeral, so after each successful or failed action, the container will exit out, forcing Nomad to recreate. Also added in a few additional packages which are more custom for my Packer templates. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://baccini-al.medium.com/how-to-containerize-a-github-actions-self-hosted-runner-5994cc08b9fb" rel="noopener noreferrer"&gt;How to containerize a GitHub Actions self-hosted runner&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://testdriven.io/blog/github-actions-docker/" rel="noopener noreferrer"&gt;Deploying Self-Hosted GitHub Actions Runners with Docker&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition to using ephemeral runners, I used the Docker plugin with Packer to build it. This gave me a chance to finally play with Docker + Packer to build an image. Pretty simple process overall. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multipurpose Packer Templates&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Took a different approach with Packer than I had done in the past.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"platform"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The list of sources to build."&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"PLATFORM"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;build_source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"source.amazon-ebs.docker"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="nx"&gt;sources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"source.amazon-ebs.docker"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;azure&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"source.azure-arm.docker"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;vsphere&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"source.vsphere-clone.docker"&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="nx"&gt;build&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;sources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build_source&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is driven by the choices made by inputs when manually kicking off the pipeline. It also allows scaling Packer templates to be much simpler when adding additional sources into the same template. I do not know why I never tried this approach in the past, but it worked out really well for the demo. &lt;/p&gt;

&lt;p&gt;Just a FYI, one of the better repositories for vSphere builds are managed by a few folks from VMware (Broadcom). Really appreciate the work they put in it, a bit more than what the average user would need. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/vmware-samples/packer-examples-for-vsphere" rel="noopener noreferrer"&gt;vmware-samples/packer-examples-for-vsphere&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Actions Pipeline&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Nothing too major here, in my experience most of my pipeline I try to keep pretty straightforward for simplicity. &lt;/p&gt;

&lt;p&gt;The GitHub Actions inputs were inspired by this repo from a HashiCorp team. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tfo-apj-demos/packer-images/blob/main/.github/workflows/packer-builds-base.yml" rel="noopener noreferrer"&gt;tfo-apj-demos/packer-images&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running Jobs on Nomad&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm going to go over a few examples of how you can run a job. I will deploy my GitHub Actions Runner to a cluster with the following methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI&lt;/li&gt;
&lt;li&gt;CLI&lt;/li&gt;
&lt;li&gt;Terraform&lt;/li&gt;
&lt;li&gt;Nomad Pack&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my talk I believe I covered the UI, CLI and Nomad Pack methods. I might have cut out the CLI for time, but I cannot remember.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scaling Nomad&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;I could be wrong, but I think the most common method for scaling with Nomad would be with the Nomad Autoscaler running as a job on the cluster: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://hub.docker.com/r/hashicorp/nomad-autoscaler" rel="noopener noreferrer"&gt;hashicorp/nomad-autoscaler&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nevertheless, there are a few options to scale Nomad: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.hashicorp.com/nomad/tools/autoscaling" rel="noopener noreferrer"&gt;Nomad Autoscaler Overview&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also worth looking at the scaling block since placement has different context on how it scales:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.hashicorp.com/nomad/docs/job-specification/scaling" rel="noopener noreferrer"&gt;Scaling Block&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Welp, that is it for now. It is late, and I am going to call it a night. Hope someone finds this useful, but my wife also thinks I need to improve my writing and communication - which is the main reason I decided to blog. &lt;/p&gt;

&lt;p&gt;Take care, &lt;br&gt;
lykins&lt;/p&gt;

</description>
      <category>nomad</category>
      <category>packer</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Terraform Professional + HashiConf Talk</title>
      <dc:creator>lykins</dc:creator>
      <pubDate>Wed, 28 Aug 2024 17:02:58 +0000</pubDate>
      <link>https://dev.to/lykins/terraform-professional-hashiconf-talk-3bhb</link>
      <guid>https://dev.to/lykins/terraform-professional-hashiconf-talk-3bhb</guid>
      <description>&lt;p&gt;Just a short little update today. I am going to be documenting a new series of blog posts. &lt;/p&gt;

&lt;h2&gt;
  
  
  HashiCorp Certified: Terraform Authoring and Operations Professional
&lt;/h2&gt;

&lt;p&gt;Took the test as part of the beta program a while back and finally got a result :)&lt;/p&gt;

&lt;p&gt;It was a fun opportunity and would recommend others to give it a shot!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Forx1b5xvs02tmls5uasc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Forx1b5xvs02tmls5uasc.png" alt="HashiCorp Certified: Terraform Authoring and Operations Professional" width="800" height="799"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  HashiConf Presentation
&lt;/h2&gt;

&lt;p&gt;So back when a call for presenters went out for HashiConf, I put out a short topic on learning Nomad. It is one of the Hashi products which has intrigued me the most. I just have not had a reason or opportunity to work with it. Especially with 2 kids aged 3 and under, the amount of free time I have to learn something new is limited. &lt;/p&gt;

&lt;p&gt;Well, unexpectedly for me this happened and now I am committed to learning a little bit about Nomad: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fvsz7fmplpasxiad3gug6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fvsz7fmplpasxiad3gug6.png" alt="HashiConf" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So if you happen to be in Boston for HashiConf, come stop by and say hi! &lt;/p&gt;

</description>
      <category>terraform</category>
    </item>
    <item>
      <title>Deploying NeuVector via Helm on minikube</title>
      <dc:creator>lykins</dc:creator>
      <pubDate>Thu, 29 Feb 2024 19:26:54 +0000</pubDate>
      <link>https://dev.to/lykins/deploying-neuvector-via-helm-on-minikube-3em6</link>
      <guid>https://dev.to/lykins/deploying-neuvector-via-helm-on-minikube-3em6</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The following will walk through the necessary steps to deploy NueVector via Helm. This can be done locally or on a virtual machine. I am using minikube to test on, but K3S/MicroK8s or any other distros will work. Since this is going to be scaled down, we will also limit the replicas. &lt;/p&gt;

&lt;p&gt;I'm going to leverage &lt;code&gt;multipass&lt;/code&gt; during this to spin up the necessary resource, but any other solution should work. &lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Required:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Virtual Machine

&lt;ul&gt;
&lt;li&gt;I will use &lt;code&gt;multipass&lt;/code&gt;, which can launch an instance with minikube already installed.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;That is really about it for this to get started. &lt;/p&gt;

&lt;h2&gt;
  
  
  Set up Virtual Machine
&lt;/h2&gt;

&lt;p&gt;Since I have multipass installed, I will launch a new vm using the existing minikube image. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;multipass launch -c 8 -m 16G -n demo minikube&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;multipass launch -c 8 -m 16G -n demo minikube                                                
Waiting for initialization to complete \
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once completed, you should get a launched.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;multipass launch -c 8 -m 16G -n demo minikube                                    
Launched: demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running a &lt;code&gt;multipass list&lt;/code&gt;, will output all the launched virtual machines.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;demo                    Running           192.168.64.20    Ubuntu 22.04 LTS
                                          172.17.0.1
                                          192.168.49.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Connect to the virtual machine, in my case, it is  &lt;code&gt;multipass shell minikube&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-92-generic aarch64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

  System information as of Thu Feb 29 09:16:40 EST 2024

  System load:                      1.5546875
  Usage of /:                       13.2% of 38.59GB
  Memory usage:                     6%
  Swap usage:                       0%
  Processes:                        199
  Users logged in:                  0
  IPv4 address for br-1746f5f95e03: 192.168.49.1
  IPv4 address for docker0:         172.17.0.1
  IPv4 address for enp0s1:          192.168.64.20
  IPv6 address for enp0s1:          fd3c:28b:5cc5:4064:5054:ff:fe87:5be
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  NeuVector Setup - minikube
&lt;/h3&gt;

&lt;p&gt;minikube is already started on the new instance; however, I am going to bump up CPUs and Memory for it. &lt;/p&gt;

&lt;p&gt;If needing to install minikube, check out the &lt;a href="https://minikube.sigs.k8s.io/docs/start/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, stop minikube:&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="gp"&gt;ubuntu@demo:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;minikube stop
&lt;span class="go"&gt;✋  Stopping node "minikube"  ...
🛑  Powering off "minikube" via SSH ...
🛑  1 node stopped.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Update CPUs:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ubuntu@demo:~$ minikube config set cpus 4
❗  These changes will take effect upon a minikube delete and then a minikube start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Update Memory:&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="gp"&gt;ubuntu@demo:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;minikube config &lt;span class="nb"&gt;set &lt;/span&gt;memory 8192
&lt;span class="go"&gt;❗  These changes will take effect upon a minikube delete and then a minikube start
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Delete exiting minikube:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ubuntu@demo:~$ minikube delete
🔥  Deleting "minikube" in docker ...
🔥  Deleting container "minikube" ...
🔥  Removing /home/ubuntu/.minikube/machines/minikube ...
💀  Removed all traces of the "minikube" cluster.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Start up new minikube:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ubuntu@demo:~$ minikube start
😄  minikube v1.32.0 on Ubuntu 22.04 (arm64)
✨  Automatically selected the docker driver. Other choices: ssh, none
📌  Using Docker driver with root privileges
👍  Starting control plane node minikube in cluster minikube
🚜  Pulling base image ...
🔥  Creating docker container (CPUs=4, Memory=8192MB) ...
🐳  Preparing Kubernetes v1.28.3 on Docker 24.0.7 ...
    ▪ Generating certificates and keys ...
    ▪ Booting up control plane ...
    ▪ Configuring RBAC rules ...
🔗  Configuring bridge CNI (Container Networking Interface) ...
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🔎  Verifying Kubernetes components...
🌟  Enabled addons: storage-provisioner, default-storageclass
🏄  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;minikube should be up running, once connected. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can check with the following command:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;minikube status&lt;/code&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="gp"&gt;ubuntu@demo:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;minikube status
&lt;span class="go"&gt;minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If looking to play with minikube more, there are additional add-ons which can be installed, in this case, we will leave the defaults, but metrics-server and dashboard are typical. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fauwcipu2tu3t0s1983y2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fauwcipu2tu3t0s1983y2.png" alt=" " width="740" height="753"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  NeuVector Setup - kubectl
&lt;/h3&gt;

&lt;p&gt;This image also comes with kubectl setup:&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="gp"&gt;ubuntu@demo:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kubectl version
&lt;span class="go"&gt;Client Version: v1.28.7
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.28.3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  NeuVector Setup - helm
&lt;/h3&gt;

&lt;p&gt;Helm is not installed, but can be quickly set up:&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="gp"&gt;ubuntu@demo:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;helm version
&lt;span class="go"&gt;Command 'helm' not found, but can be installed with:
sudo snap install helm
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To install helm on this Ubuntu instance &lt;code&gt;sudo snap install helm --classic&lt;/code&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="gp"&gt;ubuntu@demo:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;snap &lt;span class="nb"&gt;install &lt;/span&gt;helm &lt;span class="nt"&gt;--classic&lt;/span&gt;
&lt;span class="go"&gt;Download snap "core22" (1125) from channel "stable"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once install is complete, you can check the version:&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="gp"&gt;ubuntu@demo:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;helm version
&lt;span class="go"&gt;version.BuildInfo{Version:"v3.14.2", GitCommit:"c309b6f0ff63856811846ce18f3bdc93d2b4d54b", GitTreeState:"clean", GoVersion:"go1.21.7"}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  NeuVector Setup - Helm Install
&lt;/h3&gt;

&lt;p&gt;Add the helm repo:&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;helm repo add neuvector https://neuvector.github.io/neuvector-helm/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this, I'm going to use the latest version, but other older versions and development version can be listed:&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;helm search repo neuvector --devel -l
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This is the latest as of 29 February 2024 -- Leap Day!:&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="gp"&gt;ubuntu@demo:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;helm search repo neuvector
&lt;span class="go"&gt;NAME                CHART VERSION   APP VERSION DESCRIPTION
neuvector/core      2.7.3           5.3.0       Helm chart for NeuVector's core services
neuvector/crd       2.7.3           5.3.0       Helm chart for NeuVector's CRD services
neuvector/monitor   2.7.3           5.3.0       Helm chart for NeuVector monitor services
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Helm Install: &lt;/p&gt;

&lt;p&gt;For setting up NeuVector, it is simple enough that I will keep most of the default values. I am updating the controller and scanner replicas, if leaving the defaults it will nuke your system since minikube is running a single node. This is fine for local and development environments.&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;helm upgrade --install neuvector neuvector/core --version 2.7.3 \
--set tag=5.3.0 \
--set controller.replicas=1 \
--set cve.scanner.replicas=1 \
--create-namespace \
--namespace neuvector
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The readme for the repository will provide additional configuration options:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/neuvector/neuvector-helm/blob/master/charts/core/README.md" rel="noopener noreferrer"&gt;NeuVector Helm Chart&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When running:&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="gp"&gt;ubuntu@demo:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;helm upgrade &lt;span class="nt"&gt;--install&lt;/span&gt; neuvector neuvector/core &lt;span class="nt"&gt;--version&lt;/span&gt; 2.7.3 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;--set tag=5.3.0 \
--set controller.replicas=1 \
--set cve.scanner.replicas=1 \
--create-namespace \
--namespace neuvector
Release "neuvector" does not exist. Installing it now.
NAME: neuvector
LAST DEPLOYED: Thu Feb 29 09:34:30 2024
NAMESPACE: neuvector
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Get the NeuVector URL by running these commands:
&lt;/span&gt;&lt;span class="gp"&gt;  NODE_PORT=$&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;kubectl get &lt;span class="nt"&gt;--namespace&lt;/span&gt; neuvector &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{.spec.ports[0].nodePort}"&lt;/span&gt; services neuvector-service-webui&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;  NODE_IP=$&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;kubectl get nodes &lt;span class="nt"&gt;--namespace&lt;/span&gt; neuvector &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{.items[0].status.addresses[0].address}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;  echo https://$&lt;/span&gt;NODE_IP:&lt;span class="nv"&gt;$NODE_PORT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Accessing the NeuVector User Interface
&lt;/h2&gt;

&lt;p&gt;I am going to port-forward this and access it from my local browser. On the virtual machine, run the following command. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl port-forward --address 0.0.0.0 --namespace neuvector service/neuvector-service-webui 8443&lt;/code&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="gp"&gt;ubuntu@demo:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kubectl port-forward &lt;span class="nt"&gt;--address&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;--namespace&lt;/span&gt; neuvector service/neuvector-service-webui 8443
&lt;span class="gp"&gt;Forwarding from 0.0.0.0:8443 -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;8443
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will listen on port 8443 on all addresses (0.0.0.0) and forward to the service : neuvector-service-webui. &lt;/p&gt;

&lt;h3&gt;
  
  
  Accessing Locally
&lt;/h3&gt;

&lt;p&gt;*&lt;em&gt;On my local browser: *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fy1kul44b31zvpbeeytit.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fy1kul44b31zvpbeeytit.png" alt=" " width="800" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note the IP Address I pulled is the virtual machine's private IP address. This can be checked again using &lt;code&gt;multipass list&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since this is a self-signed certificate, you can ignore the warnings and proceed. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fnc35bq7eyio51xrenzcd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fnc35bq7eyio51xrenzcd.png" alt=" " width="623" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By default, username and password are admin:admin. &lt;/p&gt;

&lt;p&gt;Check off on the EULA and you can login. &lt;/p&gt;

&lt;p&gt;And voila, update admin password if you plan will continue to use this and you are done.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fpnodbmy814a84qdvj7pm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fpnodbmy814a84qdvj7pm.png" alt=" " width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Steps - Set up mysql container
&lt;/h3&gt;

&lt;p&gt;If looking to test NeuVector a bit more, we will add a MySQL service and run scans on containers and nodes with the NeuVector console.&lt;/p&gt;

&lt;p&gt;Add the bitnami repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm repo add bitnami https://charts.bitnami.com/bitnami
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm install bitnami/mysql --generate-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  In NeuVector Interface
&lt;/h4&gt;

&lt;p&gt;Go to Assets in the navigation pane on the left and select the dropdown. From the dropdown, select containers. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Turn on Auto Scan or perform a manual scan:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fk6pmdyxdcp9qqzmugevv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fk6pmdyxdcp9qqzmugevv.png" alt=" " width="406" height="137"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto Scanning:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdd3h99861bx5swh4alqn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdd3h99861bx5swh4alqn.png" alt=" " width="800" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Scans will schedule and return back results on completed. Depending on the amount of resources, both scanners and containers, it could take time. Since this is a new cluster, it is relatively quick. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Flmbkm5y69joemobixlil.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Flmbkm5y69joemobixlil.png" alt=" " width="800" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can filter and view the vulnerabilities which are found:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fudltzuirih3efkmr6x58.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fudltzuirih3efkmr6x58.png" alt=" " width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Go to the Nodes page:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fu387i62ighvetaadf2h8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fu387i62ighvetaadf2h8.png" alt=" " width="800" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see the nodes are also scanned as well for vulnerabilities. &lt;/p&gt;

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

&lt;p&gt;That is about it, a quick and easy way to test out NeuVector. This is really just scratching the surface when it comes to what features and solutions it offers.&lt;/p&gt;

</description>
      <category>minikube</category>
      <category>suse</category>
      <category>tutorial</category>
      <category>lab</category>
    </item>
    <item>
      <title>Packer Workflows with Jenkins - Building</title>
      <dc:creator>lykins</dc:creator>
      <pubDate>Thu, 29 Feb 2024 03:26:47 +0000</pubDate>
      <link>https://dev.to/lykins/part-2-packer-workflows-via-jenkins-58ob</link>
      <guid>https://dev.to/lykins/part-2-packer-workflows-via-jenkins-58ob</guid>
      <description>&lt;p&gt;This is the second part of a two-parter. Just realized I was about to write a book if I kept going. Now that we have the necessary instances and setup in part 1, we will continue and start building out our images. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: &lt;br&gt;
I am using openSUSE Leap, which will soon be discontinued and only maintained for a few years after 15.6. Alternatives would be either Tumbleweed, MicroOS, and Slowroll. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Objectives
&lt;/h2&gt;

&lt;p&gt;Primary: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a repository for Packer Templates. &lt;/li&gt;
&lt;li&gt;Use Jenkins to build the images. 

&lt;ul&gt;
&lt;li&gt;I'm going to use a Jenkinsfile as well. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Secondary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Parallel Build in AWS and Azure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Assumption is a basic knowledge of Jenkins, Packer, and Azure. I'm not going to go into much details on how to set up Azure accounts, service accounts, and some other items in this. There are basic examples out the which can be referenced. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Version Control System (GitHub, GitLab, etc.)&lt;/li&gt;
&lt;li&gt;Azure Subscription&lt;/li&gt;
&lt;li&gt;AWS Account &lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;*&lt;em&gt;I'm going to keep the repository simple. In most cases the following are needed. *&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Folder/Files necessary for CI/CD pipelines

&lt;ul&gt;
&lt;li&gt;In this case, I will end up using Jenkinsfile after all. Didn't initially plan to. &lt;/li&gt;
&lt;li&gt;If this was GitHub actions, I'd have a .github with workflows folder. For Jenkins, I'll drop them under a Jenkins directory. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Packer Folder

&lt;ul&gt;
&lt;li&gt;In this folder I also have: 

&lt;ul&gt;
&lt;li&gt;Builds: for build templates&lt;/li&gt;
&lt;li&gt;Files: used by the templatefile function or file provisioner. &lt;/li&gt;
&lt;li&gt;Scripts: for longer scripts which will be more frequently updated. I try to stay away from inline scripts with a provisioner. &lt;/li&gt;
&lt;li&gt;Var-Files: directory to store var-files which can get pulled in by the pipeline.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;*&lt;em&gt;For a tree view: *&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── jenkins
│   └── suse-linux-base
└── packer
    ├── builds
    │   ├── builds_suse.pkr.hcl
    │   └── variables.pkr.hcl
    ├── files
    │   └── README.md
    ├── scripts
    │   └── README.md
    └── var-files
        └── README.md

7 directories, 6 files

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

&lt;/div&gt;



&lt;p&gt;The repository was made public for anyone that wants to fork it as a demo. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/benjamin-lykins/demo-jenkins-packer.git" rel="noopener noreferrer"&gt;https://github.com/benjamin-lykins/demo-jenkins-packer.git&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Create a folder for packer builds.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fskpbw3fm8dzvjhc1bo84.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fskpbw3fm8dzvjhc1bo84.png" alt="Folder" width="742" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Jenkins Setup - Credentials
&lt;/h3&gt;

&lt;p&gt;The next item you will want to set up is credentials which will be used by jobs in this folder. &lt;/p&gt;

&lt;p&gt;*&lt;em&gt;On the left hand side, select &lt;code&gt;Credentials&lt;/code&gt;. *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F3qt55zyjzcbzx71qmtrh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F3qt55zyjzcbzx71qmtrh.png" alt="Credentials" width="338" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Select stores scoped to &lt;code&gt;packer builds&lt;/code&gt; or the name of the folder you created. Click on the global domain. *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdep2vnhqq7kr7cv8emgt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdep2vnhqq7kr7cv8emgt.png" alt="Scope" width="800" height="94"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Add credentials. *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fl1br5zancgh2s20e2url.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fl1br5zancgh2s20e2url.png" alt="Add Credentials" width="800" height="45"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;For credentials, we will do secret text for all Azure related credentials. Create a secret for each: *&lt;/em&gt;&lt;br&gt;
    1. Client ID (Service Account ID)&lt;br&gt;
        - &lt;code&gt;azure_client_id&lt;/code&gt;&lt;br&gt;
    2. Client Secret (Service Account Secret)&lt;br&gt;
        - &lt;code&gt;azure_client_secret&lt;/code&gt;&lt;br&gt;
    3. Subscription ID&lt;br&gt;
        - &lt;code&gt;azure_subscription_id&lt;/code&gt;&lt;br&gt;
    4. Tenant ID&lt;br&gt;
        -  &lt;code&gt;azure_tenant_id&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fvhf1w47v4hwoq2hq0tzw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fvhf1w47v4hwoq2hq0tzw.png" alt="Credential Setup" width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;When all the credentials have been added, it should look like this: *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fd1o2q46x2sgw3xbgh2s4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fd1o2q46x2sgw3xbgh2s4.png" alt="Credentials" width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Jenkins Setup - First Workflow
&lt;/h3&gt;

&lt;p&gt;This first workflow will be simple. Get the latest openSuse image and build and image. &lt;/p&gt;

&lt;p&gt;Go into the packer builds folder and create a new pipeline. I called mine &lt;code&gt;suse-linux-base&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Nothing major setup wise, for Git: *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fu1o68k0zj9htyhxdzvl8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fu1o68k0zj9htyhxdzvl8.png" alt="Pipeline Setup" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Jenkinsfile:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fh02tfp2kp0lbqy8qop64.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fh02tfp2kp0lbqy8qop64.png" alt="Jenkinsfile" width="800" height="92"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Jenkins - First Build
&lt;/h2&gt;

&lt;p&gt;If forking the repository I provided, the first run should build and run properly. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is coming from a fresh Azure Account and Subscription.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fqafm5uu8jc1rv65xag0h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fqafm5uu8jc1rv65xag0h.png" alt="Azure Welcome Screen" width="800" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No resource groups exist.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fbu2aduahm2v2jbpnbhuw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fbu2aduahm2v2jbpnbhuw.png" alt="Resource Groups" width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point we can kick off the first build. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In &lt;code&gt;suse-linux-base&lt;/code&gt;, select Build Now.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;In my case I had a small hiccup, took me a couple of tries to get it working: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Flvkfkwu4blb2727asszi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Flvkfkwu4blb2727asszi.png" alt="Pipeline Runs" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ended up changing the Create Azure Resource step a couple of times, it was running commands out of order, once I got it running, it was doing its thing properly. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;packer-rg was created.
&lt;/li&gt;
&lt;li&gt;packer-rg-temp was created as a temporary resource group to build images in. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fwiv1b01tfelgleeisj8h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fwiv1b01tfelgleeisj8h.png" alt="Resource Groups" width="308" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build Completion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can see the build was completed in the both Jenkins and in the Azure console. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fh1u705y077470zs9oy7g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fh1u705y077470zs9oy7g.png" alt="Managed Image" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create VM from Image&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;From the image, select create VM. Once ready, ssh to connect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openSUSE Leap 15.5 x86_64 (64-bit)

If you are using extensions consider to enable the auto-update feature
of the extension agent and restarting the service. As root execute:
  - sed -i s/AutoUpdate.Enabled=n/AutoUpdate.Enabled=y/ /etc/waagent.conf
  - rcwaagent restart

As "root" use the:
- zypper command for package management
- yast command for configuration management

Have a lot of fun...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that is it. Pretty straight-forward setup. If this is all I wanted, then this would be the stopping point. Very basic usage of Packer, but looking to expand upon what is possible. &lt;/p&gt;

&lt;h2&gt;
  
  
  Parallel Builds
&lt;/h2&gt;

&lt;p&gt;One of my old jobs I was a vSphere administrator. Every month the virtual machine templates would need to be patched and updated in some way. It was a very manual process and we did not use a tool like Packer to automate or store the configuration in code. It was document and click-ops heavy. &lt;/p&gt;

&lt;p&gt;Now, I will not be building images in vSphere, but I will like to build the same image in two public clouds, AWS and Azure. &lt;/p&gt;

&lt;h3&gt;
  
  
  AWS - Setup
&lt;/h3&gt;

&lt;p&gt;Nothing major needed here, you will need two items. Packer will use default VPC and subnet.  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AWS Secret and Key &lt;/li&gt;
&lt;li&gt;Subscription to openSUSE Leap&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fwy9b56cym2nq64aatbhq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fwy9b56cym2nq64aatbhq.png" alt="openSUSE Leap Subscription" width="800" height="109"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Packer - Setup
&lt;/h3&gt;

&lt;p&gt;To set this up, I will need to add in an additional plugin to the configuration. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create New Branch&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;I am going to create a new branch called &lt;code&gt;parallel-aws-azure&lt;/code&gt; for this use case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;packer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;azure&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github.com/hashicorp/azure"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 2"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;amazon&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github.com/hashicorp/amazon"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1"&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;&lt;strong&gt;Add a new source block for AWS.&lt;/strong&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Check for ssh_username: &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/managing-users.html" rel="noopener noreferrer"&gt;Default user names&lt;/a&gt;. In this scenario &lt;code&gt;ec2-user&lt;/code&gt; or &lt;code&gt;root&lt;/code&gt; would have worked.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"amazon-ebs"&lt;/span&gt; &lt;span class="s2"&gt;"suse"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami_name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"suse-image-${local.time}"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.micro"&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-2"&lt;/span&gt;
  &lt;span class="nx"&gt;source_ami_filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"openSUSE-Leap-*-hvm-ssd-x86_64-*"&lt;/span&gt;
      &lt;span class="nx"&gt;root-device-type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ebs"&lt;/span&gt;
      &lt;span class="nx"&gt;virtualization-type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hvm"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;most_recent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;owners&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"679593333241"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;ssh_username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ec2-user"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And lastly, update the build block with the new source.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;sources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"source.azure-arm.suse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"source.amazon-ebs.suse"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"shell"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;inline&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"echo foo"&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;h3&gt;
  
  
  Jenkins - Setup
&lt;/h3&gt;

&lt;p&gt;I'll keep the same jenkinsfile for this. All I will update is adding credentials for AWS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    environment {
        AZURE_SUBSCRIPTION_ID = credentials('azure_subscription_id')
        AZURE_TENANT_ID = credentials('azure_tenant_id')
        AZURE_CLIENT_ID = credentials('azure_client_id')
        AZURE_CLIENT_SECRET = credentials('azure_client_secret')
        AWS_ACCESS_KEY_ID= credentials('aws_access_key_id')
        AWS_SECRET_ACCESS_KEY= credentials('aws_secret_access_key')
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, ensure the&lt;code&gt;packer build&lt;/code&gt; folder has these keys. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fih10ivbqfox4knclcjvl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fih10ivbqfox4knclcjvl.png" alt="AWS secrets" width="800" height="69"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create New Pipeline&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I called mine &lt;code&gt;parallel-build&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Point to the new branch: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F65f9ugf0ywodcwnkl9pm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F65f9ugf0ywodcwnkl9pm.png" alt="Point to new branch" width="800" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep the same Jenkinsfile path: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fihnn3w888k57spfytykt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fihnn3w888k57spfytykt.png" alt="Jenkinsfile path" width="800" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once ready, hit build and see how it goes. &lt;/p&gt;

&lt;p&gt;Packer by default will run both builds concurrently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1;32mazure-arm.suse: output will be in this color.[0m
[1;36mamazon-ebs.suse: output will be in this color.[0m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When completed, Packer will output the AMI id and Azure managed image: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Packer:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;==&amp;gt; Builds finished. The artifacts of successful builds are:
--&amp;gt; amazon-ebs.suse: AMIs were created:
us-east-2: ami-09de620f124c9bc00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Azure:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--&amp;gt; azure-arm.suse: Azure.ResourceManagement.VMImage:

OSType: Linux
ManagedImageResourceGroupName: packer-rg
ManagedImageName: suse-image-20240226195516
ManagedImageId: /subscriptions/****/resourceGroups/packer-rg/providers/Microsoft.Compute/images/suse-image-20240226195516
ManagedImageLocation: East US
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In AWS console:&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F2rwk8ij964utbyx3xgd6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F2rwk8ij964utbyx3xgd6.png" alt="AWS AMI" width="800" height="80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In Azure console:&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fqclm7zbbkvfvgeuefcu8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fqclm7zbbkvfvgeuefcu8.png" alt="Azure Image" width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nothing too drastically different when it comes to parallel builds in multiple public clouds when setting up in Jenkins.&lt;/p&gt;

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

&lt;p&gt;This is a very surface level example of using Packer with Jenkins. Additional use cases would be: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Layering Builds

&lt;ul&gt;
&lt;li&gt;Reference newly build images and add components necessary for hosting an application, such as IIS or Tomcat on the host. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Chaining Pipelines

&lt;ul&gt;
&lt;li&gt;This can be done similar to layering builds. Have the base image pipeline run first to build a base image, if success, have a subsequent pipeline trigger to build on the image built in the base pipeline. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Automate Provisioning of Images

&lt;ul&gt;
&lt;li&gt;When a new image is built, trigger a Terraform Cloud workspace to run a plan and apply to pick up the latest image. 

&lt;ul&gt;
&lt;li&gt;In AWS, Autoscaling Groups can trigger a refresh. 

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_group#triggers" rel="noopener noreferrer"&gt;https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_group#triggers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;Automate Testing of Images

&lt;ul&gt;
&lt;li&gt;I honestly do not have many ideas for this. For me if an image is built with Packer and can be provisioned whether in AWS or Azure, and finally an application can be deployed on it, then that is enough for me. I could see security and compliance tools. Probably just do not have the exposure for this. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>packer</category>
      <category>jenkins</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
