<?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>Forem: Steven Marks</title>
    <description>The latest articles on Forem by Steven Marks (@marksie1988).</description>
    <link>https://forem.com/marksie1988</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%2F1074827%2Fc50efb45-a2c7-4ddf-a51c-8e8e204d9c77.png</url>
      <title>Forem: Steven Marks</title>
      <link>https://forem.com/marksie1988</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/marksie1988"/>
    <language>en</language>
    <item>
      <title>Ditch the Manual Chore: Automating Releases and Versioning with release-please</title>
      <dc:creator>Steven Marks</dc:creator>
      <pubDate>Tue, 04 Nov 2025 08:25:18 +0000</pubDate>
      <link>https://forem.com/marksie1988/ditch-the-manual-chore-automating-releases-and-versioning-with-release-please-2kp0</link>
      <guid>https://forem.com/marksie1988/ditch-the-manual-chore-automating-releases-and-versioning-with-release-please-2kp0</guid>
      <description>&lt;p&gt;I love coding, I love seeing new features come to life, and I genuinely enjoy writing documentation (I know, weird right?). But if there's one part of the development lifecycle that consistently makes me want to scream into my pillow, it's the tedious, error-prone chore of creating a new release.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Updating the version number manually? Check.&lt;/li&gt;
&lt;li&gt;Scouring Git history to draft a meaningful &lt;code&gt;CHANGELOG.md&lt;/code&gt;? Check.&lt;/li&gt;
&lt;li&gt;Forgetting a fix and having to immediately push a &lt;code&gt;v1.2.1-fix-for-the-fix&lt;/code&gt; patch? &lt;em&gt;Absolutely&lt;/em&gt; check.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've been following my journey on automating my workflows, you'll know that I'm a huge believer in moving the mundane to the machine. I previously discussed streamlining development using &lt;strong&gt;Semantic Release&lt;/strong&gt; and &lt;strong&gt;Commitizen&lt;/strong&gt; in my article, &lt;a href="https://totaldebug.uk/posts/automated-release-with-semantic-release-and-commitizen/" rel="noopener noreferrer"&gt;Automated release with Semantic Release and Commitizen&lt;/a&gt;. This approach dramatically cleaned up commit messages and helped determine the correct version bump.&lt;/p&gt;

&lt;p&gt;However, over time I have found that with semantic-release, there are some limitations if you are trying to move over to trunk based development and are not in a position to use feature flags. &lt;/p&gt;

&lt;p&gt;The missing piece? &lt;strong&gt;&lt;code&gt;release-please&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is &lt;code&gt;release-please&lt;/code&gt; and How Does It Improve the Workflow?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;release-please&lt;/code&gt; is an open-source tool developed by Google, primarily implemented as a GitHub Action, that takes over the final, messy steps of the release process.&lt;/p&gt;

&lt;p&gt;The fundamental improvement over a self-hosted Semantic Release setup is simple:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Old Way (Semantic Release/Custom Script)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;New Way (&lt;code&gt;release-please&lt;/code&gt; Action)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trigger&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual command or a final merge/tag event.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Continuously&lt;/strong&gt; tracks changes on the main branch.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Release Artifacts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Generated by script, then pushed/tagged manually or via subsequent CI job.&lt;/td&gt;
&lt;td&gt;Manages a &lt;strong&gt;Perpetual Release PR&lt;/strong&gt; for instant preview.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Human Action&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manually run a command/pipeline and hope it works.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Merge the Release PR&lt;/strong&gt; to confirm and publish.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The core of &lt;code&gt;release-please&lt;/code&gt; is that it constantly maintains a living, breathing view of your next release right inside your GitHub Pull Requests tab, operating entirely on the principles of &lt;strong&gt;Conventional Commits&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Core Logic: Commits to Versions
&lt;/h3&gt;

&lt;p&gt;The tool reads the commit history on your main branch to determine the exact nature of the change:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Commit Type&lt;/th&gt;
&lt;th&gt;Semantic Version Impact&lt;/th&gt;
&lt;th&gt;Example of Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fix:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;PATCH&lt;/strong&gt; (0.0.x)&lt;/td&gt;
&lt;td&gt;A backward-compatible bug fix.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;feat:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;MINOR&lt;/strong&gt; (0.x.0)&lt;/td&gt;
&lt;td&gt;A new, backward-compatible feature.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;feat!:&lt;/code&gt; or &lt;code&gt;BREAKING CHANGE:&lt;/code&gt; in footer&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;MAJOR&lt;/strong&gt; (x.0.0)&lt;/td&gt;
&lt;td&gt;An incompatible API change.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You stop having to worry about which version number to apply; you just commit changes with accurate commit messages, and &lt;code&gt;release-please&lt;/code&gt; figures out the rest.&lt;/p&gt;

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

&lt;p&gt;First off, it's important to know that &lt;strong&gt;release-please will not trigger a release if there is nothing to release!&lt;/strong&gt; If no new &lt;code&gt;feat&lt;/code&gt; or &lt;code&gt;fix&lt;/code&gt; commits have landed, it just sits tight.&lt;/p&gt;

&lt;p&gt;When it does have work to do, here’s the flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Detect Changes:&lt;/strong&gt; The action runs (either on a schedule or when I push to the main branch) and scans the repository's history to find all the changes since the last release tag.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Analyse Commits:&lt;/strong&gt; It then analyzes all the commit messages, looking for Conventional Commit keywords (like &lt;code&gt;feat&lt;/code&gt;, &lt;code&gt;fix&lt;/code&gt;, &lt;code&gt;chore&lt;/code&gt;, or &lt;code&gt;BREAKING CHANGE&lt;/code&gt;) to categorize everything that's happened.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Determine Version Bump:&lt;/strong&gt; Based on those commits, it automatically determines the correct semantic version bump (major, minor, or patch).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generate Release PR:&lt;/strong&gt; This is the best part. The action creates (or updates) a special Pull Request. This PR contains the new version number bumped in files like &lt;code&gt;package.json&lt;/code&gt; and, crucially, a fully generated changelog based on all the commits it found.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Merge Release PR:&lt;/strong&gt; Once I've reviewed that PR and I'm happy it looks correct, I just merge it into the main branch. This merge event is the trigger for the next step.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Publish Release:&lt;/strong&gt; Merging the PR kicks off another workflow that creates the official Git tag, drafts the formal GitHub release (using the changelog), and publishes the new version to package registries, like npm for my JavaScript projects.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Implementation: Configuration and Action
&lt;/h2&gt;

&lt;p&gt;Getting started is surprisingly simple and requires minimal configuration for most projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Configuration File (&lt;code&gt;release-please-config.json&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;For most projects, defining a single file to tell the tool what type of project it is, is enough. This example sets up a generic project, ensuring the tool knows where to look for version updates.&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;"release-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"simple"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include-v-in-tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;
  
  
  2. Manifest file (&lt;code&gt;.release-please-manifest.json&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The manifest file tracks the version that the project is currently on, for existing projects enter the latest release tag, for a new project use &lt;code&gt;0.0.0&lt;/code&gt; to tell release-please its a new project.&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;"."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.0"&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;
  
  
  3. The Github Action
&lt;/h3&gt;

&lt;p&gt;The core of the implementation lives in your repository's CI/CD pipeline, often in a file named &lt;code&gt;.github/workflows/release-please.yaml&lt;/code&gt;. This action needs the appropriate permissions to create the release artifacts (PRs, Tags, and GitHub Releases).&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# The 'write' permission is essential for creating/updating the release PR,&lt;/span&gt;
  &lt;span class="c1"&gt;# creating the Git tag, and publishing the GitHub release.&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;pull-request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release-please&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;google-github-actions/release-please-action@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# Use your configuration file&lt;/span&gt;
          &lt;span class="na"&gt;config-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release-please-config.json&lt;/span&gt;
          &lt;span class="na"&gt;manifest-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.release-please-manifest.json&lt;/span&gt;
          &lt;span class="c1"&gt;# a PAT is needed if you need to re-run tests against an existing PR&lt;/span&gt;
          &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${secrets.MY_PAT}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once this action is merged and runs, your release automation will be live. Say goodbye to the manual version bump chore. 👋&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Hooks with release-please
&lt;/h3&gt;

&lt;p&gt;When a release is created its likely you would want to run additional jobs after it. &lt;/p&gt;

&lt;p&gt;If you would like to trigger another job after release is created, you need to propagate outputs from the step to the job:&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;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release-please&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;tag_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.release.outputs.tag_name}}&lt;/span&gt;
      &lt;span class="na"&gt;release_created&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.release.outputs.release_created}}&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;googleapis/release-please-action@v4&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;release&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;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MY_PAT }}&lt;/span&gt;
          &lt;span class="na"&gt;config-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release-please-config.json&lt;/span&gt;
          &lt;span class="na"&gt;manifest-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.release-please-manifest.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Trigger additional workflows
&lt;/h3&gt;

&lt;p&gt;Creating the release PR is only half the battle. The real value comes from using it to trigger our staging pipeline, allowing for proper user testing before the release is finalised.&lt;/p&gt;

&lt;p&gt;We want to build and push a new 'release candidate' (RC) image to our container registry every time the &lt;code&gt;release-please&lt;/code&gt; PR is opened or updated. This ensures our staging environment always has the very latest proposed code.&lt;/p&gt;

&lt;p&gt;To do this, we can create a separate GitHub Actions workflow that listens to &lt;code&gt;pull_request&lt;/code&gt; events on the &lt;code&gt;main&lt;/code&gt; branch. The magic lies in adding a condition to ensure it only runs for PRs created by &lt;code&gt;release-please&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the complete workflow file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create Staging artifact from Release PR&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;opened&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;synchronize&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-deploy-staging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="c1"&gt;# This 'if' condition is the key:&lt;/span&gt;
    &lt;span class="c1"&gt;# It checks that the source branch name starts with the prefix&lt;/span&gt;
    &lt;span class="c1"&gt;# used by release-please.&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;startsWith(github.head_ref, 'release-please--')&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt; &lt;span class="c1"&gt;# Assuming auth with GCP/AWS/Azure&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code from PR&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&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;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.pull_request.head.sha }}&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;📝 Extract version from manifest file&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;extract_version&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;# Example output: 1.1.0&lt;/span&gt;
          &lt;span class="s"&gt;VERSION=$(jq -r '.["."]' .github/release-please-manifest.json)&lt;/span&gt;
          &lt;span class="s"&gt;echo "version=${VERSION}" &amp;gt;&amp;gt; $GITHUB_OUTPUT&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;🔢 Calculate Next Staging Tag&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;calculate-staging-tag&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;set -eo pipefail&lt;/span&gt;
          &lt;span class="s"&gt;# The base version (e.g., 1.1.0) from the previous step&lt;/span&gt;
          &lt;span class="s"&gt;BASE_VERSION="${{ steps.extract_version.outputs.version }}"&lt;/span&gt;
          &lt;span class="s"&gt;# Assumes IMG env var is set (e.g., ghcr.io/my-org/my-repo)&lt;/span&gt;
          &lt;span class="s"&gt;IMAGE_NAME="${{ env.IMG }}"&lt;/span&gt;

          &lt;span class="s"&gt;echo "Base version detected: $BASE_VERSION"&lt;/span&gt;

          &lt;span class="s"&gt;# 1. Query registry for existing tags matching the pattern X.Y.Z-rc.N&lt;/span&gt;
          &lt;span class="s"&gt;# We search for tags like '1.1.0-rc.1', '1.1.0-rc.2', etc.&lt;/span&gt;
          &lt;span class="s"&gt;# This example uses gcloud, adjust for your registry (e.g., 'az acr', 'docker search')&lt;/span&gt;
          &lt;span class="s"&gt;TAGS=$(gcloud container images list-tags "$IMAGE_NAME" \&lt;/span&gt;
            &lt;span class="s"&gt;--format="json(tags)" \&lt;/span&gt;
            &lt;span class="s"&gt;--filter="tags~^${BASE_VERSION}-rc\.[0-9]+$" 2&amp;gt;/dev/null || echo "[]")&lt;/span&gt;

          &lt;span class="s"&gt;# 2. Determine the next sequential number&lt;/span&gt;
          &lt;span class="s"&gt;if [ "$TAGS" == "[]" ]; then&lt;/span&gt;
              &lt;span class="s"&gt;# No matching tags found, start at .1&lt;/span&gt;
              &lt;span class="s"&gt;NEXT_NUM=1&lt;/span&gt;
              &lt;span class="s"&gt;echo "No previous rc tags found. Starting at rc.1."&lt;/span&gt;
          &lt;span class="s"&gt;else&lt;/span&gt;
              &lt;span class="s"&gt;# Extract the number from tags, sort numerically, and take the highest&lt;/span&gt;
              &lt;span class="s"&gt;MAX_NUM=$(echo "$TAGS" | jq -r --arg V "$BASE_VERSION" '.[] | .tags[] | select(startswith($V + "-rc.")) | split(".")[-1] | tonumber' | sort -rn | head -n 1)&lt;/span&gt;

              &lt;span class="s"&gt;if [ -z "$MAX_NUM" ] || [ "$MAX_NUM" -eq 0 ]; then&lt;/span&gt;
                  &lt;span class="s"&gt;NEXT_NUM=1&lt;/span&gt;
              &lt;span class="s"&gt;else&lt;/span&gt;
                  &lt;span class="s"&gt;# Increment the highest number found&lt;/span&gt;
                  &lt;span class="s"&gt;NEXT_NUM=$((MAX_NUM + 1))&lt;/span&gt;
              &lt;span class="s"&gt;fi&lt;/span&gt;
              &lt;span class="s"&gt;echo "Highest previous rc number found: ${MAX_NUM}. Next number is: ${NEXT_NUM}"&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;

          &lt;span class="s"&gt;# 3. Output the final tag (e.g., 1.1.0-rc.2)&lt;/span&gt;
          &lt;span class="s"&gt;NEW_TAG="${BASE_VERSION}-rc.${NEXT_NUM}"&lt;/span&gt;
          &lt;span class="s"&gt;echo "NEW_TAG=${NEW_TAG}"&lt;/span&gt;
          &lt;span class="s"&gt;echo "staging_tag=$NEW_TAG" &amp;gt;&amp;gt; $GITHUB_OUTPUT&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Docker Buildx&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/setup-buildx-action@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Push Staging Image&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v6&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;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.IMG }}:${{ steps.calculate-staging-tag.outputs.staging_tag }}&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;linux/amd64&lt;/span&gt;
          &lt;span class="na"&gt;cache-from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha&lt;/span&gt;
          &lt;span class="na"&gt;cache-to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha,mode=max&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's quickly break down the most important parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;if&lt;/code&gt; condition: &lt;code&gt;if: startsWith(github.head_ref, 'release-please--')&lt;/code&gt; is the controller. It ensures this expensive build job only runs on pull requests opened by &lt;code&gt;release-please&lt;/code&gt;, not on regular developer feature branches.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Extract version&lt;/code&gt;: This step reads the &lt;code&gt;.github/release-please-manifest.json&lt;/code&gt; file to find out what version &lt;code&gt;release-please&lt;/code&gt; is proposing (e.g., &lt;code&gt;1.1.0&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Calculate Next Staging Tag&lt;/code&gt;: This is the core logic. It asks the container registry "What is the highest rc tag you have for version &lt;code&gt;1.1.0&lt;/code&gt;?" If it finds &lt;code&gt;1.1.0-rc.1&lt;/code&gt;, it will output &lt;code&gt;1.1.0-rc.2&lt;/code&gt;. If it finds nothing, it starts at &lt;code&gt;1.1.0-rc.1&lt;/code&gt;. This ensures that every push to the PR (e.g., a fix) generates a new, unique, and sequential artifact.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With this workflow in place, we now have a fully automated process: a developer merges a feature, &lt;code&gt;release-please&lt;/code&gt; creates a PR, and that PR automatically triggers a new staging build, ready for testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Great Debate: To Squash or Not to Squash?
&lt;/h2&gt;

&lt;p&gt;When you automate releases based on commit history, the question of how you merge feature branches becomes critically important.&lt;/p&gt;

&lt;p&gt;The goal is a version history that is both clean for maintainers (Google's reasoning) and descriptive for developers (my reasoning).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Case for Linear History (Squash-Merge)
&lt;/h3&gt;

&lt;p&gt;Many organisations, including Google, advocate for using squash-merges to maintain a linear Git history. Their reasons often centre on maintenance efficiency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clean History:&lt;/strong&gt; The main branch remains uncluttered, showing only one logically grouped commit per feature or bug fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simpler Tooling:&lt;/strong&gt; Tools like git bisect (for tracking down which change introduced a bug) work flawlessly because every commit is guaranteed to be a passing, production-ready state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changelog Control:&lt;/strong&gt; Internal commits like a small fix introduced during feature development are irrelevant to the release notes as they were never experienced on the main branch. Squashing hides this noise from the public-facing &lt;code&gt;CHANGELOG.md&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Counter-Argument (Logical Merging)
&lt;/h3&gt;

&lt;p&gt;While the logic for a clean history is sound, many developers, myself included, find monolithic squash commits challenging to work with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context is King:&lt;/strong&gt; When you squash all feature branch commits into one, you risk losing the detailed context of where the fix or feature was applied. This forces one to scroll a lot to find context, which is "not good."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Descriptive History:&lt;/strong&gt; A small number of logically grouped commits provides a history that is both informative and manageable. We should strive for fewer commits, but use "common sense" to combine them into fewer, more meaningful entries.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Recommended Hybrid Approach
&lt;/h3&gt;

&lt;p&gt;The great news is that &lt;code&gt;release-please&lt;/code&gt; is flexible enough to accommodate both philosophies.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;For maximum simplicity and tool compatibility (The Squash Style):&lt;/strong&gt; Use &lt;strong&gt;squash-merge&lt;/strong&gt;. Crucially, leverage the commit message's body to include all relevant &lt;code&gt;feat:&lt;/code&gt; and &lt;code&gt;fix:&lt;/code&gt; messages using footers. &lt;code&gt;release-please&lt;/code&gt; is smart enough to parse multiple changes from a single monolithic commit:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;feat: adds v4 UUID to crypto

This adds support for v4 UUIDs to the library.

fix(utils): unicode no longer throws exception
  BREAKING-CHANGE: encode method no longer throws.
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The additional messages must be added to the bottom of the commit body.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;For maximum contextual integrity (The Logical Style):&lt;/strong&gt; Use a &lt;strong&gt;merge commit&lt;/strong&gt; or a &lt;strong&gt;rebase merge&lt;/strong&gt; that retains your carefully crafted atomic commits. As long as every commit landing on &lt;code&gt;main&lt;/code&gt; strictly follows the Conventional Commit specification, &lt;code&gt;release-please&lt;/code&gt; will aggregate all of them when generating the Release PR.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This small automation is a huge step forward. It completely eliminates the non-coding task of versioning and changelog management, allowing you to focus on developing features and fixing bugs.&lt;/p&gt;

</description>
      <category>releaseplease</category>
      <category>automation</category>
      <category>release</category>
    </item>
    <item>
      <title>Automated release with Semantic Release and commitizen</title>
      <dc:creator>Steven Marks</dc:creator>
      <pubDate>Mon, 09 Oct 2023 19:46:46 +0000</pubDate>
      <link>https://forem.com/marksie1988/automated-release-with-semantic-release-and-commitizen-109m</link>
      <guid>https://forem.com/marksie1988/automated-release-with-semantic-release-and-commitizen-109m</guid>
      <description>&lt;p&gt;When working with JavaScript projects, managing version numbers and commit messages is important for the maintainability of the project. Since 2020 I have been the main developer of &lt;a href="https://github.com/totaldebug/atomic-calendar-revive"&gt;Atomic Calendar Revive&lt;/a&gt; a highly customisable Home Assistant calendar card, I found maintaining versions and releases to be cumbersome until recently. In this article, I will introduce the &lt;a href="https://github.com/commitizen/cz-cli"&gt;commitizen&lt;/a&gt; and &lt;a href="https://github.com/semantic-release/semantic-release"&gt;semantic-release&lt;/a&gt; packages for creation or appropriate commit messages and semantic versioning. I will also provide examples of how I am currently using these packages to streamline my release workflow and project maintenance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The old days
&lt;/h2&gt;

&lt;p&gt;Starting out I had never developed a project like this in TypeScript, I had only ever worked on Python projects, So I was running &lt;code&gt;yarn run build&lt;/code&gt; which would run rollup, build my js files into a dist folder, I would then commit the changes to a branch, create a PR, merge the PR then manually tag the new version and create the release on GitHub.&lt;/p&gt;

&lt;p&gt;As you can see there are quite a few manual steps to achieve this which took too much time, time I could be spending on new features or bug fixes.&lt;/p&gt;

&lt;p&gt;I knew there had to be a better way to do this, there was no way large teams were wasting this much time on releases and as a small one person dev, its even more important to save as much time as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Semantic-release and how does it work?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;semantic-release&lt;/strong&gt; uses the commit message to determine the impact of changes in the codebase, with this it is able to automate updating the version number correctly and managing the release process for the project.&lt;/p&gt;

&lt;p&gt;Semantic-release performs the following basic operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Analyses commit messages to determine whether a new version is required.&lt;/li&gt;
&lt;li&gt;If a new version is required, automatically determines the appropriate version number.&lt;/li&gt;
&lt;li&gt;Updates the CHANGELOG file and creates the relevant Git tag.&lt;/li&gt;
&lt;li&gt;Publishes a github release if required&lt;/li&gt;
&lt;li&gt;Publishes the new version to the package manager if required.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are many other actions that it can perform via a great plugin architecture, so these are not limited to github / npm.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Commitizen and how does it work?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;commitizen&lt;/strong&gt; helps developers write commit messages in the same format, this also ensures that all commit messages follow the semantic versioning requirements.&lt;/p&gt;

&lt;p&gt;Commitizen provides an interactive interface that prompts developers for specific information relating to that change, it then generates the commit message in the correct format, ensuring compatibility with semantic versions which ensures semantic-release can read the commit messages as expected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nDZhDgJP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6h0gdr2puhftxtl2nrk0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nDZhDgJP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6h0gdr2puhftxtl2nrk0.png" alt="Commitizen TUI" width="800" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to setup Semantic-release and Commitizen
&lt;/h2&gt;

&lt;p&gt;Below is a guide on how to use Semantic-release and Commitizen packages in your project, these are settings that I currently use but you can amend them to better suit your project.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install Semantic-release and Commitizen packages
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; semantic-release commitizen cz-conventional-changelog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you find you are unable to use the &lt;code&gt;git cz&lt;/code&gt; command after using the above install try this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; commitizen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will install commitizen globally which seems to resolve the issue.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a configuration file for &lt;code&gt;semantic-release&lt;/code&gt; (&lt;code&gt;.release.rc&lt;/code&gt;, &lt;code&gt;release.config.js&lt;/code&gt; or &lt;code&gt;package.json&lt;/code&gt;) an example of how I use &lt;code&gt;package.json&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"release"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"plugins"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="s2"&gt;"@semantic-release/commit-analyzer"&lt;/span&gt;&lt;span class="p"&gt;,&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;span class="nl"&gt;"preset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"conventionalcommits"&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;span class="p"&gt;],&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;span class="s2"&gt;"@semantic-release/release-notes-generator"&lt;/span&gt;&lt;span class="p"&gt;,&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;span class="nl"&gt;"preset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"conventionalcommits"&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;span class="p"&gt;],&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;span class="s2"&gt;"@semantic-release/npm"&lt;/span&gt;&lt;span class="p"&gt;,&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;span class="nl"&gt;"npmPublish"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;span class="p"&gt;],&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;span class="s2"&gt;"@semantic-release/exec"&lt;/span&gt;&lt;span class="p"&gt;,&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;span class="nl"&gt;"prepareCmd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yarn run build"&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;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"@semantic-release/changelog"&lt;/span&gt;&lt;span class="p"&gt;,&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;span class="s2"&gt;"@semantic-release/git"&lt;/span&gt;&lt;span class="p"&gt;,&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;span class="nl"&gt;"assets"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="s2"&gt;"package.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="s2"&gt;"changelog"&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;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chore(release): ${nextRelease.version} [skip ci]&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;${nextRelease.notes}"&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;span class="p"&gt;],&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;span class="s2"&gt;"@semantic-release/github"&lt;/span&gt;&lt;span class="p"&gt;,&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;span class="nl"&gt;"assets"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dist/atomic-calendar-revive.js"&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;span class="p"&gt;]&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;span class="p"&gt;]&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;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;This configuration file contains settings used for semantic-release. Lets break this down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@semantic-release/commit-analyzer&lt;/code&gt;: Analyses commit messages and determines how the version number should be incremented (major, minor, patch). I use the conventional commits format so set the preset to inform commit-analyzer, this will look out for the &lt;code&gt;!&lt;/code&gt; to signify breaking changes etc.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@semantic-release/release-notes-generator&lt;/code&gt;: Generates the release notes based on commit messages related to the new version.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@semantic-release/npm&lt;/code&gt;: Updates the package.json file and publishes to the NPM package manager, I don't publish to NPM manager so disabled this but i do need package.json updating with the latest version.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@semantic-release/exec&lt;/code&gt;: This executes a command, in this case it will build my project ready for uploading to GitHub.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@semantic-release/changelog&lt;/code&gt;: Creates or updates a CHANGELOG file based on the generated release notes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@semantic-release/git&lt;/code&gt;: Commits changes related to the new version to the git repository and creates the relevant git tag. I use this to commit the updated package.json into git and it also adds a commit message.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@semantic-release/github&lt;/code&gt;: Publishes the new version to GitHub and creates the related GitHub release. I also upload the file generated by &lt;code&gt;@semantic-release/exec&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Configure the &lt;code&gt;cz-conventional-changelog&lt;/code&gt; adapter in &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&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;"config"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"commitizen"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"path"&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_modules/cz-conventional-changelog"&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;span class="p"&gt;}&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;ol&gt;
&lt;li&gt;Configure CI / CD, I use Github Actions in the below example:
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;beta&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout 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@v2&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Node.js&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/setup-node@v2&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn install&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;Run Semantic Release&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx semantic-release&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;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to use semantic-release and commitizen
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Commitizen
&lt;/h3&gt;

&lt;p&gt;Simply run &lt;code&gt;git add .&lt;/code&gt; then &lt;code&gt;git cz&lt;/code&gt;, this command will run the interactive interface of Commitizen and ask you to write a properly formatted commit message.&lt;/p&gt;

&lt;h3&gt;
  
  
  Semantic Release
&lt;/h3&gt;

&lt;p&gt;Semantic release by default uses the following branches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;master&lt;/strong&gt; Regular releases to the default distribution channel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;N.N.x or N.x.x or N.x with N being a number&lt;/strong&gt; Regular releases to a distribution channel matching the branch name from any existing branch with a name matching a maintenance release range&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;next&lt;/strong&gt; Regular releases to the next distribution channel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;next-major&lt;/strong&gt; Regular releases to the next-major distribution channel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;beta (pre-release)&lt;/strong&gt; pre-releases to the beta distribution channel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;alpha (pre-release)&lt;/strong&gt; pre-releases to the alpha distribution channel from the branch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't need to use all of these branches, for example I currently only use the  &lt;code&gt;beta&lt;/code&gt; and &lt;code&gt;master&lt;/code&gt; branches, All my development happens in beta, on commit it releases a pre-release version then once i'm happy its working I merge &lt;code&gt;beta&lt;/code&gt; into &lt;code&gt;master&lt;/code&gt; which will create the latest production release.&lt;/p&gt;

&lt;h2&gt;
  
  
  How are version numbers determined from commit messages
&lt;/h2&gt;

&lt;p&gt;Semantic release wil analyze the commit message to determine what the new version number should be. This process works by analysing the words and prefixes in the commit message. Commitizen facilitates this by ensuring the conventional commits format is followed.&lt;/p&gt;

&lt;p&gt;The conventional commit format states that commit messages should be formatted as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;type&amp;gt;([optional scope]): &amp;lt;summary&amp;gt;

[optional body]

[optional footer(s)]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;type&lt;/strong&gt;: Indicates the type of change (e.g., fix, feat, chore, docs, refactor, test, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;[optional scope]&lt;/strong&gt; (optional): Describes the part of the project that the change is applied to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;summary&lt;/strong&gt;: A concise description of the change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;[optional body]&lt;/strong&gt; (optional): A more detailed description of the change if required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;[optional footer(s)]&lt;/strong&gt; (optional): Add tags to issues / reviewers etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Semantic-release uses this information to determine how to update the version number:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If at least one of the commit messages is &lt;code&gt;feat&lt;/code&gt; type, the new version number will be subject to a “minor” increase (&lt;code&gt;0.1.0&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;If at least one of the commit messages contains &lt;code&gt;BREAKING CHANGE&lt;/code&gt; in the body or &lt;code&gt;!&lt;/code&gt; after the type, the new version number will be subject to a &lt;strong&gt;major&lt;/strong&gt; increase (&lt;code&gt;1.0.0&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;In other cases, especially if at least one of the commit messages is &lt;code&gt;fix&lt;/code&gt; type, the new version number will be subject to a &lt;strong&gt;patch&lt;/strong&gt; increase (&lt;code&gt;0.0.1&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;With the help of semantic-release and commitizen packages, you can increase the quality and maintainability of your project by using automated versioning and creating appropriate, easy to understand commit messages. Due to this being compatible with CI/CD it also makes the development process more efficient saving precious time to be spent on improving the project or drinking coffee.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Automating deployments using Terraform with Proxmox and ansible</title>
      <dc:creator>Steven Marks</dc:creator>
      <pubDate>Sun, 04 Jun 2023 20:23:16 +0000</pubDate>
      <link>https://forem.com/marksie1988/automating-deployments-using-terraform-with-proxmox-and-ansible-2cd2</link>
      <guid>https://forem.com/marksie1988/automating-deployments-using-terraform-with-proxmox-and-ansible-2cd2</guid>
      <description>&lt;p&gt;Over the years my home lab has grown and become more and more difficult to maintain, especially because some servers I build and forget as they function so well.&lt;/p&gt;

&lt;p&gt;I have found recently though that moving to newer versions of operating systems can be difficult for the servers that I cant easily containerise at the moment.&lt;/p&gt;

&lt;p&gt;For this reason I have moved over to using Terraform with Proxmox and ansible.&lt;/p&gt;

&lt;p&gt;Telemate developed a Terraform provider that maps Terraform functionality to the Proxmox API, so start by defining the use of that provider in &lt;code&gt;provider.tf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;=0.13.0"&lt;/span&gt;

  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;proxmox&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;"telmate/proxmox"&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;"2.9.14"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"proxmox"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# Configuration options&lt;/span&gt;
  &lt;span class="nx"&gt;pm_api_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;proxmox_api_url&lt;/span&gt;
  &lt;span class="nx"&gt;pm_api_token_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;proxmox_api_token_id&lt;/span&gt;
  &lt;span class="nx"&gt;pm_api_token_secret&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;proxmox_api_token_secret&lt;/span&gt;

  &lt;span class="c1"&gt;# Optional: skip TLS Verification&lt;/span&gt;
  &lt;span class="nx"&gt;pm_tls_insecure&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;pm_parallel&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="nx"&gt;pm_timeout&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we see two sections, the first of which contains the configuration for Terraform, it specifies the version of Terraform that is required along with all of the required providers and their required versions.&lt;/p&gt;

&lt;p&gt;The second section contains the provider itself, and the configuration, a full list of arguments can be found &lt;a href="https://github.com/Telmate/terraform-provider-proxmox/blob/master/docs/index.md#argument-reference"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A provider is a plugin that allows Terraform to manage external APIs (such as proxmox)&lt;/p&gt;

&lt;p&gt;Now this is no good without some servers or "resources", I create a file per resource for my lab, this keeps it simple for me, however you may want to do this differently depending on your requirements. Create a file with the resource name &lt;code&gt;bastion.tf&lt;/code&gt; There is a lot more going on in this file, so I have added comments to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Specify the resource type, and then the name&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"proxmox_vm_qemu"&lt;/span&gt; &lt;span class="s2"&gt;"bastion"&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;"td-bast01"&lt;/span&gt; &lt;span class="c1"&gt;# Name to call the VM&lt;/span&gt;
  &lt;span class="nx"&gt;desc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Bastion"&lt;/span&gt; &lt;span class="c1"&gt;# Description for the VM&lt;/span&gt;
  &lt;span class="nx"&gt;target_node&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;proxmox_host&lt;/span&gt; &lt;span class="c1"&gt;# Proxmox target node&lt;/span&gt;

  &lt;span class="nx"&gt;clone&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;template_name&lt;/span&gt;  &lt;span class="c1"&gt;# The name of the template that this resource will be created from&lt;/span&gt;

  &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;# is the qemu agent installed?&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;"cloud-init"&lt;/span&gt; &lt;span class="c1"&gt;# The OS type of the image clone&lt;/span&gt;
  &lt;span class="nx"&gt;cores&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="c1"&gt;# number of CPU cores&lt;/span&gt;
  &lt;span class="nx"&gt;sockets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;# number of CPU sockets&lt;/span&gt;
  &lt;span class="nx"&gt;cpu&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"host"&lt;/span&gt; &lt;span class="c1"&gt;# The CPU type&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt; &lt;span class="c1"&gt;# Amount of memory to allocate&lt;/span&gt;
  &lt;span class="nx"&gt;onboot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;# start the VM on host startup&lt;/span&gt;
  &lt;span class="nx"&gt;scsihw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"virtio-scsi-pci"&lt;/span&gt; &lt;span class="c1"&gt;# Scsi hardware type&lt;/span&gt;
  &lt;span class="nx"&gt;bootdisk&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"scsi0"&lt;/span&gt; &lt;span class="c1"&gt;# The boot disk scsi&lt;/span&gt;

  &lt;span class="c1"&gt;# This section contains the disk configuration, it can be duplicated for additional disks&lt;/span&gt;
  &lt;span class="nx"&gt;disk&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"30G"&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;"scsi"&lt;/span&gt;
    &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"local-thin"&lt;/span&gt;
    &lt;span class="nx"&gt;iothread&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="c1"&gt;# if you want two NICs, just copy this whole network section and duplicate it&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;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"virtio"&lt;/span&gt;
    &lt;span class="nx"&gt;bridge&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vmbr0"&lt;/span&gt;
    &lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;ipconfig0&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ip=172.16.20.43/24,gw=172.16.20.1"&lt;/span&gt;
  &lt;span class="nx"&gt;nameserver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"172.16.20.1"&lt;/span&gt;

  &lt;span class="nx"&gt;serial&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;"socket"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;# sshkeys set using variables. the variable contains the text of the key.&lt;/span&gt;
  &lt;span class="nx"&gt;sshkeys&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;
  ${var.ssh_key}
&lt;/span&gt;&lt;span class="no"&gt;  EOF

&lt;/span&gt;  &lt;span class="c1"&gt;# Terraform has provisioners that allow the execution of commands / scripts on a local or remote machine.&lt;/span&gt;
  &lt;span class="c1"&gt;# Here I execute a command to update the VM.&lt;/span&gt;
  &lt;span class="k"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"remote-exec"&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;"sudo apt update"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"sudo apt upgrade -y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"echo Done!"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nx"&gt;connection&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;host&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"172.16.20.43"&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;"ssh"&lt;/span&gt;
      &lt;span class="nx"&gt;user&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu"&lt;/span&gt;
      &lt;span class="nx"&gt;private_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key_path&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="c1"&gt;# Here I execute an ansible playbook to configure the VM.&lt;/span&gt;
  &lt;span class="c1"&gt;# I specify ANSIBLE_HOST_KEY_CHECKING as if run a second time and the VM is rebuil ansible wont connect unless this is set to false.&lt;/span&gt;
  &lt;span class="k"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"local-exec"&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;"ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -u &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ansible_user&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;  -l bastion -i ../ansible-deploy/inventory --private-key &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key_path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -e 'pub_key=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_key_path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' --ssh-extra-args '-o UserKnownHostsFile=/dev/null' ../ansible-deploy/main.yml"&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;Through all of the files creates you will have noticed variables have been used against various configuration parameters, before they will work they need to be defined in a file, we will call this &lt;code&gt;vars.tf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# provider vars&lt;/span&gt;
&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"proxmox_api_url"&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"proxmox_api_token_id"&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"proxmox_api_token_secret"&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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# resource vars&lt;/span&gt;
&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"proxmox_host"&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;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"td-vh01"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"template_name"&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;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"jammy-template"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"ansible_user"&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="s2"&gt;"ubuntu"&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"private_key_path"&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"public_key_path"&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"ssh_key"&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;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINm8L9RZ4lDsRDm6Zx7jqOrQx9mO7FphqcrV5teyGVJN"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Now that we have defined the variables create a credential file to store all our variable information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;proxmox_api_url&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://127.0.0.1:8006/api2/json"&lt;/span&gt;
&lt;span class="nx"&gt;proxmox_api_token_id&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nx"&gt;proxmox_api_token_secret&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nx"&gt;private_key_path&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~/.ssh/id_ed25519"&lt;/span&gt;
&lt;span class="nx"&gt;public_key_path&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~/.ssh/id_ed25519.pub"&lt;/span&gt;
&lt;span class="nx"&gt;ansible_user&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Don't commit this file to Git as it contains sensitive information&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Any variables in &lt;code&gt;vars.tf&lt;/code&gt; that have a default value don't need to be defined in the credential file if the default value is sufficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cloud-Init template
&lt;/h2&gt;

&lt;p&gt;The configuration that is used utilises a cloud-init template, check out my previous post (&lt;a href="https://totaldebug.uk/posts/proxmox-template-with-cloud-image-and-cloud-init/"&gt;Proxmox template with cloud image and cloud init&lt;/a&gt;) where I cover how to set this up for use in Proxmox with Terraform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Now all of the files we require are created, lets get it running:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install Terraform and Ansible&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; terraform ansible
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enter the directory where your Terraform files reside&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;terraform init&lt;/code&gt;, this will initialize your Terraform configuration and pull all the required providers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure that you have the &lt;code&gt;credential.auto.tfvars&lt;/code&gt; file created and with your variables populated&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;terraform plan -out plan&lt;/code&gt; and if everything seems good &lt;code&gt;terraform apply&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Use &lt;code&gt;terraform apply --auto-approve&lt;/code&gt; to automatically apply without a prompt&lt;/p&gt;

&lt;p&gt;To destroy the infrastructure, run &lt;code&gt;terraform destroy&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;There is so much more potential using Terraform and Ansible. I have just scratched the surface, but you could automate everything up to firewall configuration as well, this is something I still need to look into, but it would be great to deploy and configure the firewall based on each individual device.&lt;/p&gt;

&lt;p&gt;If you have any cool ideas for using Terraform and Ansible please let me know in the comments below!&lt;/p&gt;

&lt;p&gt;Until next time...&lt;/p&gt;

</description>
      <category>proxmox</category>
      <category>ansible</category>
      <category>terraform</category>
      <category>devops</category>
    </item>
    <item>
      <title>Proxmox Template with Cloud Image and Cloud Init</title>
      <dc:creator>Steven Marks</dc:creator>
      <pubDate>Sat, 03 Jun 2023 22:02:57 +0000</pubDate>
      <link>https://forem.com/marksie1988/proxmox-template-with-cloud-image-and-cloud-init-3660</link>
      <guid>https://forem.com/marksie1988/proxmox-template-with-cloud-image-and-cloud-init-3660</guid>
      <description>&lt;p&gt;Using Cloud images and Cloud init with Proxmox is the quickest, most efficient way to deploy servers at this time. Cloud images are small cloud certified that have Cloud init pre-installed and ready to accept configuration.&lt;/p&gt;

&lt;p&gt;Cloud images and Cloud init also work with Proxmox and if you combine this with Terraform  you have a fully automated deployment model. &lt;/p&gt;

&lt;h2&gt;
  
  
  Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Download image
&lt;/h3&gt;

&lt;p&gt;First you will need to choose an &lt;a href="https://cloud-images.ubuntu.com/"&gt;Ubuntu Cloud Image&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rather than downloading this, copy the URL.&lt;/p&gt;

&lt;p&gt;Then SSH into your Proxmox server and run wget with the URL you just copied, similar to below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will download the image onto your proxmox server ready for use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install packages
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;qemu-guest-agent&lt;/code&gt; is not installed on the cloud-images, so we need a way to inject that into out image file. This can be done with a great tool called &lt;code&gt;virt-customize&lt;/code&gt; this is installed with the package &lt;code&gt;libguestfs-tools&lt;/code&gt;. &lt;a href="https://www.libguestfs.org/"&gt;libguestfs&lt;/a&gt; is a set of tools for accessing and modifying virtual machine (VM) disk images.&lt;/p&gt;

&lt;p&gt;Install:&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 update &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;libguestfs-tools &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install &lt;code&gt;qemu-guest-agent&lt;/code&gt; into the downloaded image:&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;virt-customize &lt;span class="nt"&gt;-a&lt;/span&gt; jammy-server-cloudimg-amd64.img &lt;span class="nt"&gt;--install&lt;/span&gt; qemu-guest-agent &lt;span class="nt"&gt;--run-command&lt;/span&gt; &lt;span class="s1"&gt;'systemctl enable qemu-guest-agent.service'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also install other packages at this point.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding users to the image (Optional)
&lt;/h3&gt;

&lt;p&gt;It is possible to also add a user and SSH keys with the &lt;code&gt;virt-customize&lt;/code&gt;. This is useful for automation such as terraform.&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;virt-customize &lt;span class="nt"&gt;-a&lt;/span&gt; jammy-server-cloudimg-amd64.img &lt;span class="nt"&gt;--run-command&lt;/span&gt; &lt;span class="s1"&gt;'useradd simone'&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;virt-customize &lt;span class="nt"&gt;-a&lt;/span&gt; jammy-server-cloudimg-amd64.img &lt;span class="nt"&gt;--run-command&lt;/span&gt; &lt;span class="s1"&gt;'mkdir -p /home/simone/.ssh'&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;virt-customize &lt;span class="nt"&gt;-a&lt;/span&gt; jammy-server-cloudimg-amd64.img &lt;span class="nt"&gt;--ssh-inject&lt;/span&gt; simone:file:id_ed25519.pub
&lt;span class="nb"&gt;sudo &lt;/span&gt;virt-customize &lt;span class="nt"&gt;-a&lt;/span&gt; jammy-server-cloudimg-amd64.img &lt;span class="nt"&gt;--run-command&lt;/span&gt; &lt;span class="s1"&gt;'chown -R simone:simone /home/simone'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Adds the user to the image&lt;/li&gt;
&lt;li&gt;Makes the SSH Key directory&lt;/li&gt;
&lt;li&gt;Injects the SSH Key. &lt;code&gt;simone&lt;/code&gt; is the user the key will apply to, &lt;code&gt;file:id_ed25519.pub&lt;/code&gt; is the file on the local host where the SSH Key is located&lt;/li&gt;
&lt;li&gt;Makes sure the user simone owns the home folder&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Create a virtual machine from the image
&lt;/h3&gt;

&lt;p&gt;Now we need to create a new virtual machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;qm create 9000 &lt;span class="nt"&gt;--memory&lt;/span&gt; 2048 &lt;span class="nt"&gt;--core&lt;/span&gt; 2 &lt;span class="nt"&gt;--name&lt;/span&gt; jammy-template &lt;span class="nt"&gt;--net0&lt;/span&gt; virtio,bridge&lt;span class="o"&gt;=&lt;/span&gt;vmbr0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import the downloaded Ubuntu disk to the correct storage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;qm importdisk 9000 jammy-server-cloudimg-amd64.img local-lvm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Attach the new disk as a SCSI drive on the SCSI Controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;qm &lt;span class="nb"&gt;set &lt;/span&gt;9000 &lt;span class="nt"&gt;--scsihw&lt;/span&gt; virtio-scsi-pci &lt;span class="nt"&gt;--scsi0&lt;/span&gt; local-lvm:vm-9000-disk-0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add cloud init drive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;qm &lt;span class="nb"&gt;set &lt;/span&gt;9000 &lt;span class="nt"&gt;--ide2&lt;/span&gt; local-lvm:cloudinit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make the could init drive bootable and restrict BIOS to boot from disk only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;qm &lt;span class="nb"&gt;set &lt;/span&gt;9000 &lt;span class="nt"&gt;--boot&lt;/span&gt; c &lt;span class="nt"&gt;--bootdisk&lt;/span&gt; scsi0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add serial console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;qm &lt;span class="nb"&gt;set &lt;/span&gt;9000 &lt;span class="nt"&gt;--serial0&lt;/span&gt; socket &lt;span class="nt"&gt;--vga&lt;/span&gt; serial0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turn on guest agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;qm &lt;span class="nb"&gt;set &lt;/span&gt;9000 &lt;span class="nt"&gt;--agent&lt;/span&gt; &lt;span class="nv"&gt;enabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;DO NOT POWER ON THE VM&lt;br&gt;
{: .prompt-warning }&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Convert the VM to a Template
&lt;/h3&gt;

&lt;p&gt;Now, Create a template from the image you just created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;qm template 9000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Clone the template to a VM
&lt;/h2&gt;

&lt;p&gt;Now you have a fully functioning template, which can be cloned as much as you want. But it makes sense to set some of the settings.&lt;/p&gt;

&lt;p&gt;First, clone the VM (here we are cloning the template with ID 9000 to a new VM with ID 999):&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;qm clone 9000 999 &lt;span class="nt"&gt;--name&lt;/span&gt; test-cloud-init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, set the SSH keys (if you didn't add yours earlier) and IP address:&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;qm &lt;span class="nb"&gt;set &lt;/span&gt;999 &lt;span class="nt"&gt;--sshkey&lt;/span&gt; ~/.ssh/id_rsa.pub
&lt;span class="nb"&gt;sudo &lt;/span&gt;qm &lt;span class="nb"&gt;set &lt;/span&gt;999 &lt;span class="nt"&gt;--ipconfig0&lt;/span&gt; &lt;span class="nv"&gt;ip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.10.1.20/24,gw&lt;span class="o"&gt;=&lt;/span&gt;10.10.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s now ready to start up!&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;qm start 999
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should be able to log in without any problems (after trusting the SSH fingerprint). Note that the username is &lt;code&gt;ubuntu&lt;/code&gt;, for the key set here. If you added your own user earlier, you can use that instead&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh ubuntu@10.10.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once happy with the template, you can stop the VM and clean up the resources:&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;qm stop 999 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;qm destroy 999
&lt;span class="nb"&gt;rm &lt;/span&gt;jammy-server-cloudimg-amd64.img
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://registry.terraform.io/modules/sdhibit/cloud-init-vm/proxmox/latest/examples/ubuntu_single_vm"&gt;https://registry.terraform.io/modules/sdhibit/cloud-init-vm/proxmox/latest/examples/ubuntu_single_vm&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Hopefully this information was useful for you, If you have any questions about this article and share your thoughts head over to my &lt;a href="https://discord.gg/6fmekudc8Q"&gt;Discord&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>proxmox</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
