<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0"><channel><title>Pulumi Blog: IaC Best Practices Series</title><link>https://www.pulumi.com/blog/series/iac-best-practices/</link><description>Pulumi blog posts: IaC Best Practices Series.</description><language>en-us</language><pubDate>Mon, 08 Apr 2024 00:00:00 +0000</pubDate><item><title>IaC Best Practices: Summarizing Key Learnings</title><link>https://www.pulumi.com/blog/iac-best-practices-summarizing-key-learnings/</link><pubDate>Mon, 08 Apr 2024 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/iac-best-practices-summarizing-key-learnings/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/iac-best-practices-summarizing-key-learnings/index.png" /&gt;
&lt;p&gt;Welcome to the final post in our series of articles focused on Infrastructure as Code best practices. In this post, we&amp;rsquo;ll wrap up our recommendations for &lt;a href="https://www.pulumi.com/product/infrastructure-as-code/"&gt;IaC with Pulumi&lt;/a&gt;, summarizing the best practices from previous posts as well as highlighting some areas of potential future growth for the team at Zephyr Archaeotech Emporium&amp;mdash;the fictional company at the center of the story throughout this series.&lt;/p&gt;
&lt;p&gt;For ease of navigation, here are links to all the blog posts in the series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-understanding-code-organization-stacks/"&gt;IaC Best Practices: Understanding Code Organization and Stacks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-enabling-developer-stacks-git-branches/"&gt;IaC Best Practices: Enabling Developer Stacks and Git Branches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-structuring-pulumi-projects/"&gt;IaC Best Practices: Structuring Pulumi Projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-applying-stack-references/"&gt;IaC Best Practices: Applying Stack References&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-implementing-rbac-and-security/"&gt;IaC Best Practices: Implementing RBAC and Security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-using-automation-api/"&gt;IaC Best Practices: Using Automation API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IaC Best Practices: Summarizing Key Learnings&lt;/strong&gt; (this post)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="recapping-iac-best-practices"&gt;Recapping IaC Best Practices&lt;/h2&gt;
&lt;p&gt;As this series has progressed, we&amp;rsquo;ve been showing you how Zephyr&amp;rsquo;s use of Pulumi has changed in response to how the organization, the team, and the application has changed. In case you haven&amp;rsquo;t been following along&amp;mdash;or in case you need a refresher&amp;mdash;here&amp;rsquo;s a brief summary of what&amp;rsquo;s happened in the series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Zephyr started with a single project in a single repository with two stacks, one for production and one for testing. &lt;em&gt;(Details are available in &lt;a href="https://www.pulumi.com/blog/iac-best-practices-understanding-code-organization-stacks/"&gt;the first blog post&lt;/a&gt; in the series.)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Zephyr quickly added per-developer stacks as a way to enhance developer productivity. &lt;em&gt;(You can read about per-developer stacks in &lt;a href="https://www.pulumi.com/blog/iac-best-practices-enabling-developer-stacks-git-branches/"&gt;the second post&lt;/a&gt; in the series.)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;As Zephyr continued to grow, their single Pulumi project grew into three different Pulumi projects: one for base infrastructure, one for their Kubernetes platform, and one for the online store application. &lt;em&gt;(The &lt;a href="https://www.pulumi.com/blog/iac-best-practices-structuring-pulumi-projects/"&gt;third post in the series&lt;/a&gt; describes the reasoning for adopting multiple projects.)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Accompanying the switch to multiple projects, Zephyr implemented stack references (to pass information between stacks in different projects) and applied role-based access control (RBAC) to the stacks in Pulumi Cloud. &lt;em&gt;(Refer back to &lt;a href="https://www.pulumi.com/blog/iac-best-practices-applying-stack-references/"&gt;the fourth post&lt;/a&gt; and &lt;a href="https://www.pulumi.com/blog/iac-best-practices-implementing-rbac-and-security/"&gt;the fifth post&lt;/a&gt; in the series for more details on each of these areas.)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Zephyr realized a need to split out their data layer, going from three projects to four projects, and along the way saw an opportunity to add a higher level of orchestration using the Pulumi Automation API. &lt;em&gt;(Refer back to &lt;a href="https://www.pulumi.com/blog/iac-best-practices-using-automation-api/"&gt;our sixth post&lt;/a&gt; for more details on Zephyr&amp;rsquo;s initial use of Automation API.)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since the last blog post, the Zephyr team has continued to explore &lt;a href="https://www.pulumi.com/docs/using-pulumi/automation-api/"&gt;Automation API&lt;/a&gt;, and has added an Automation API program that allows their developers to deploy the Zephyr online store directly from the associated GitHub repositories&amp;mdash;developers don&amp;rsquo;t even need to clone down the repositories first.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;To see the latest Automation API programs for Zephyr, refer to &lt;a href="https://github.com/pulumi-zephyr/zephyr-automation"&gt;the &lt;code&gt;zephyr-automation&lt;/code&gt; GitHub repository&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="areas-of-future-growth-for-zephyr"&gt;Areas of future growth for Zephyr&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;ve stated before that best practices are always point-in-time recommendations based on the requirements of the organization/team/individual using Pulumi. What this means, in practice, is that there is almost always room for growth and evolution. Organizations, teams, and individuals rarely remain static, and therefore their use of Pulumi is also unlikely to remain entirely static.&lt;/p&gt;
&lt;p&gt;So, what are some potential areas of future growth for Zephyr?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Splitting the deployment of the microservices&lt;/strong&gt;: We alluded to this &lt;a href="https://www.pulumi.com/blog/iac-best-practices-using-automation-api/"&gt;in the previous post&lt;/a&gt;. Right now, all the microservices that comprise the online store are deployed together. What if one particular team needs to deploy only their service? The current approach can certainly accommodate that&amp;mdash;Pulumi is declarative and will only change what needs to be changed&amp;mdash;but what&amp;rsquo;s better for the Zephyr team? Does Zephyr need to optimize for allowing service teams to move independently? Is it about reducing blast radius, so that undesired changes aren&amp;rsquo;t introduced to another service? These are the sorts of considerations that drive this decision (and the third post in our series discusses &lt;a href="https://www.pulumi.com/blog/iac-best-practices-structuring-pulumi-projects/"&gt;structuring Pulumi projects&lt;/a&gt; in more detail).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Using component resources&lt;/strong&gt;: Currently, the Zephyr team is using the standard API objects supplied by the Pulumi provider. For example, the various microservices for the Zephyr online store are defined using standard Kubernetes objects, like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assetsNs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;k8s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;assets-ns&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;: &lt;span class="kt"&gt;...&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assetsService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;k8s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;assets-service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;: &lt;span class="kt"&gt;...&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assetsDeployment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;k8s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Deployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;assets-deployment&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;: &lt;span class="kt"&gt;...&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://www.pulumi.com/docs/concepts/resources/components/"&gt;Component resources&lt;/a&gt; would allow the Zephyr team to define a single logical resource that contains &amp;ldquo;child&amp;rdquo; resources. This would allow the Zephyr team to create a &lt;code&gt;Microservice&lt;/code&gt; resource that incorporates the standard Kubernetes &lt;code&gt;Namespace&lt;/code&gt;, &lt;code&gt;Deployment&lt;/code&gt;, and &lt;code&gt;Service&lt;/code&gt; objects. Deploying an instance of a &lt;code&gt;Microservice&lt;/code&gt; resource deploys all the child objects defined in the component resource. This can simplify the Pulumi code through further use of &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself"&gt;DRY (Don&amp;rsquo;t Repeat Yourself)&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Making use of Pulumi Deployments&lt;/strong&gt;: Zephyr&amp;rsquo;s CI/CD pipelines are not a topic we&amp;rsquo;ve discussed in the series. Given that Zephyr uses GitHub, they could use GitHub Actions (GHA); Pulumi has &lt;a href="https://github.com/pulumi/actions"&gt;an action for GHA workflows&lt;/a&gt;. Zephyr could also use &lt;a href="https://www.pulumi.com/product/pulumi-deployments/"&gt;Pulumi Deployments&lt;/a&gt;, which offers an extensive set of features&amp;mdash;OIDC support for dynamic credentials, Review Stacks (per-PR stacks), support for GitHub Enterprise, and GitOps-style workflows, to name a few. Support for Pulumi Deployments is also baked into Automation API, meaning that Zephyr could extend their existing Automation API code to incorporate Pulumi Deployments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Addressing secrets and configuration management&lt;/strong&gt;: We&amp;rsquo;ve also not discussed how Zephyr could manage their secrets and configuration data. &lt;a href="https://www.pulumi.com/product/esc/"&gt;Pulumi ESC&lt;/a&gt; is a good option here, offering seamless integration with Pulumi for infrastructure as code while also supporting a variety of &lt;a href="https://www.pulumi.com/what-is/infrastructure-as-code-for-devops/"&gt;other DevOps tools and use cases&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even though it&amp;rsquo;s working well, clearly there&amp;rsquo;s a lot of room for the Zephyr team to continue to enhance and improve their infrastructure as code implementation!&lt;/p&gt;
&lt;h2 id="summarizing-best-practices"&gt;Summarizing Best Practices&lt;/h2&gt;
&lt;p&gt;Throughout the series, we&amp;rsquo;ve provided guidelines on how to best utilize Pulumi. While we recommend reading the entire series, in this section will &amp;ldquo;summarize our summaries&amp;rdquo; and provide some broad, overarching practices.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;With regard to Pulumi projects, &lt;strong&gt;keep it as simple as possible, but no simpler.&lt;/strong&gt; While tools like Automation API can help with orchestrating multiple projects, it remains true that in most cases a single project is simpler. Use multiple projects when necessary, but only when necessary.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use stacks freely.&lt;/strong&gt; Using multiple projects has a cost in complexity. Using multiple stacks, on the other hand, does not&amp;mdash;so use them freely to model different environments, to provide per-developer test environments, to test changes before rolling them into production, or for any other use case where you need a separate, independent instance of the infrastructure defined in your Pulumi program.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focus on the requirements.&lt;/strong&gt; It can sometimes be too easy to focus on the technology instead of the requirements. There are a &lt;em&gt;lot&lt;/em&gt; of very cool things you can do with Pulumi, but try not to get distracted from the reason you&amp;rsquo;re using infrastructure as code: repeatable, reliable, consistent deployments of your cloud infrastructure.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t forget about security along the way.&lt;/strong&gt; Make sure you are properly securing your infrastructure. Pulumi &lt;a href="https://www.pulumi.com/crossguard"&gt;CrossGuard&lt;/a&gt; can help here by providing &amp;ldquo;policy as code&amp;rdquo; guardrails for cloud infrastructure. Pulumi Cloud&amp;rsquo;s RBAC functionality helps with ensuring the right users have the right permissions to the right sets of cloud infrastructure resources.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="reviewing-the-code"&gt;Reviewing the Code&lt;/h2&gt;
&lt;p&gt;You can view the code we&amp;rsquo;ve presented in this series in the following GitHub repositories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://github.com/pulumi-zephyr/zephyr-infra"&gt;&amp;ldquo;zephyr-infra&amp;rdquo; repository&lt;/a&gt; contains the base infrastructure definitions.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/pulumi-zephyr/zephyr-k8s"&gt;&amp;ldquo;zephyr-k8s&amp;rdquo; repository&lt;/a&gt; contains the Pulumi code to stand up the Kubernetes cluster onto which Zephyr deploys their online store application.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/pulumi-zephyr/zephyr-data"&gt;&amp;ldquo;zephyr-data&amp;rdquo; repository&lt;/a&gt; manages Zephyr&amp;rsquo;s &amp;ldquo;data layer&amp;rdquo; for persistent data used by the online store application.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/pulumi-zephyr/zephyr-app"&gt;&amp;ldquo;zephyr-app&amp;rdquo; repository&lt;/a&gt; is Zephyr&amp;rsquo;s online store. This repository has both the application code as well as the Pulumi code for deploying the application (see the &amp;ldquo;infra&amp;rdquo; directory).&lt;/li&gt;
&lt;li&gt;In the &lt;a href="https://github.com/pulumi-zephyr/zephyr-automation"&gt;&amp;ldquo;zephyr-automation&amp;rdquo; repository&lt;/a&gt; you&amp;rsquo;ll find Automation API programs used by Zephr to orchestrate deployments of the different projects involved in supporting the online store.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In each of these repositories, there are different branches that reflect the state of the code as of a particular blog post. The &amp;ldquo;main&amp;rdquo; branch reflects the final state of the code, as described in this wrap-up post.&lt;/p&gt;
&lt;h2 id="wrapping-up"&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;This series has used the story of a fictional company, Zephyr Archaeotech Emporium, to discuss some recommendations around how to best utilize Pulumi for solving their infrastructure as code needs. While this company is fictional, the benefits of Pulumi and infrastructure as code are real. We invite you to try for yourself&amp;mdash;sign up for &lt;a href="https://app.pulumi.com/signup"&gt;a free Pulumi Cloud account&lt;/a&gt; today!&lt;/p&gt;
&lt;div class="note note-tip"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;&lt;strong&gt;You might also like:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/your-perfect-infrastructure/"&gt;
Your Perfect Infrastructure May Not Be So Perfect
&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;</description><author>Scott Lowe</author><category>best-practices</category></item><item><title>IaC Best Practices: Using Automation API</title><link>https://www.pulumi.com/blog/iac-best-practices-using-automation-api/</link><pubDate>Wed, 26 Jul 2023 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/iac-best-practices-using-automation-api/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/iac-best-practices-using-automation-api/index.png" /&gt;
&lt;p&gt;Welcome to the sixth post in our series of blog posts focused on Infrastructure as Code (IaC) best practices. So far in this series, you&amp;rsquo;ve seen how Zephyr Archaeotech Emporium&amp;mdash;the fictional company at the center of this series&amp;mdash;uses Pulumi to manage their online retail store. You read how Zephyr&amp;rsquo;s initial use of Pulumi changed to use short-lived per-developer stacks. Later, as Zephyr continued to grow, you saw how Zephyr restructured their Pulumi projects and stacks, incorporated Stack References, and used Pulumi Cloud&amp;rsquo;s role-based access control (RBAC) functionality to control access to their stacks. This post focuses on how Zephyr takes advantage of the &lt;a href="https://www.pulumi.com/docs/using-pulumi/automation-api/concepts-terminology"&gt;Pulumi Automation API&lt;/a&gt; to bring an even greater level of orchestration to the stacks that represent their online store.&lt;/p&gt;
&lt;p&gt;The ultimate goal of this series is to discuss best practices for using Pulumi to manage a fairly complex containerized application. These practices have unfolded organically, as a direct response to Zephyr&amp;rsquo;s evolving needs. The aim is to demonstrate that best practices are not set in stone, but are rather &amp;ldquo;point in time&amp;rdquo; recommendations that adapt as your company grows.&lt;/p&gt;
&lt;p&gt;For ease of navigation, here are links to all the posts in the series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-understanding-code-organization-stacks/"&gt;IaC Best Practices: Understanding Code Organization and Stacks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-enabling-developer-stacks-git-branches/"&gt;IaC Best Practices: Enabling Developer Stacks and Git Branches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-structuring-pulumi-projects/"&gt;IaC Best Practices: Structuring Pulumi Projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-applying-stack-references/"&gt;IaC Best Practices: Applying Stack References&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-implementing-rbac-and-security/"&gt;IaC Best Practices: Implementing RBAC and Security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IaC Best Practices: Using Automation API&lt;/strong&gt; (you are here)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-summarizing-key-learnings"&gt;IaC Best Practices: Summarizing Key Learnings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="managing-complexity-in-a-multi-project-pulumi-setup"&gt;Managing Complexity in a Multi-Project Pulumi Setup&lt;/h2&gt;
&lt;p&gt;Over the course of this series, you&amp;rsquo;ve seen how Zephyr&amp;rsquo;s use of Pulumi has grown and changed. For the sake of completeness, here&amp;rsquo;s a quick recap:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Zephyr started with a single project in a single repository with two stacks, one for production and one for testing. &lt;em&gt;(Details are available in &lt;a href="https://www.pulumi.com/blog/iac-best-practices-understanding-code-organization-stacks/"&gt;understanding code organization and stacks&lt;/a&gt; post.)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;To unlock developer velocity, Zephyr quickly added per-developer stacks. &lt;em&gt;(You can read about &lt;a href="https://www.pulumi.com/blog/iac-best-practices-enabling-developer-stacks-git-branches/"&gt;per-developer stacks&lt;/a&gt; post in the series.)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;As Zephyr grew and expanded, their single Pulumi project grew into three different Pulumi projects: one for base infrastructure, one for their Kubernetes platform, and one for the online store application. &lt;em&gt;(The &lt;a href="https://www.pulumi.com/blog/iac-best-practices-structuring-pulumi-projects/"&gt;structuring Pulumi projects&lt;/a&gt; post describes the reasoning for adopting multiple projects.)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Accompanying the switch to multiple projects, Zephyr implemented stack references (to pass information between stacks in different projects) and applied role-based access control (RBAC) to the stacks in Pulumi Cloud. &lt;em&gt;(Refer back to the &lt;a href="https://www.pulumi.com/blog/iac-best-practices-applying-stack-references/"&gt;stack references&lt;/a&gt; and &lt;a href="https://www.pulumi.com/blog/iac-best-practices-implementing-rbac-and-security/"&gt;RBAC &amp;amp; security&lt;/a&gt; posts in the series for more details on each of these areas.)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;To see the state of Zephyr&amp;rsquo;s Pulumi implementation before the changes described in this article, please look at the &lt;code&gt;blog/multi-project&lt;/code&gt; branch in each of the GitHub repositories (&lt;code&gt;zephyr-infra&lt;/code&gt;, &lt;code&gt;zephyr-k8s&lt;/code&gt;, and &lt;code&gt;zephyr-app&lt;/code&gt;).&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Since we last checked in on Zephyr, one significant change has emerged. As traffic to the Zephyr online store continued to increase, Zephyr&amp;rsquo;s operations team was finding it difficult to scale the relational databases running on Kubernetes (specifically, the orders and catalog databases). After evaluating their options, the team decided to migrate to &lt;a href="https://aws.amazon.com/rds/aurora/"&gt;Amazon Aurora&lt;/a&gt; with MySQL compatibility. Using the considerations outlined for structuring Pulumi projects, they decided to create a fourth Pulumi project, named &amp;ldquo;zephyr-data&amp;rdquo;, to manage the data infrastructure for the online store. Initially, this data infrastructure project manages only the Aurora clusters and database instances for the orders and catalog databases.&lt;/p&gt;
&lt;p&gt;Because Zephyr was already using a modular approach to their overall application stack, inserting a new project for managing the online store&amp;rsquo;s data layer involved only minimal changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A new Pulumi program had to be written. This new program used stack references to leverage the existing infrastructure created by the base infrastructure stack and allowed traffic to the databases from the security group created by the Kubernetes platform stack. You&amp;rsquo;ll find this new program in &lt;a href="https://github.com/pulumi/zephyr-data/"&gt;the &lt;code&gt;zephyr-data&lt;/code&gt; repository&lt;/a&gt; on GitHub (look in the &lt;code&gt;blog/automation-api&lt;/code&gt; branch).&lt;/li&gt;
&lt;li&gt;The Pulumi program for the Kubernetes platform stack only needed to export the security group created for the cluster nodes.&lt;/li&gt;
&lt;li&gt;The Pulumi program for deploying the online store microservices referenced the database endpoints created by the new data infrastructure stack, and no longer spun up containerized instances of MariaDB for the orders and catalog databases.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The resulting four project architecture looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="four-project-architecture.jpg" alt="An illustration of Zephyr&amp;rsquo;s four project architecture"&gt;&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;To see the state of the code after the changes described above&amp;mdash;the addition of the &lt;code&gt;zephyr-data&lt;/code&gt; project and the changes to use Amazon Aurora&amp;mdash;please review the &lt;code&gt;blog/automation-api&lt;/code&gt; branch in each of the Zephyr repositories.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Using a modular approach with multiple projects has its benefits. However, one of the trade-offs of splitting your IaC code into separate Pulumi projects is some additional relative complexity. With additional complexity comes additional room for error. To stand up a production-like environment, developers now had to stand up four different stacks (base infrastructure, Kubernetes platform, data infrastructure, and online store) in a specific order. If a user gets the order wrong, then the online store won&amp;rsquo;t come up properly.&lt;/p&gt;
&lt;p&gt;With only four projects, this is not an insurmountable challenge by any stretch. However, what if it was 10 projects? Or 15 projects? Those numbers are not out of reach! Right now Zephyr has a single project to deploy all the various services involved for the online store. What if the application team decides to split up that project so that individual services can be deployed independently of each other? (After all, being able to deploy microservices independently of one another is one of the benefits of such an architecture.)&lt;/p&gt;
&lt;p&gt;The Zephyr team started asking some questions: How can we make this process simpler? How can we remove room for error? How can we prepare our teams for greater scale?&lt;/p&gt;
&lt;h2 id="simplifying-advanced-deployments-with-automation-api"&gt;Simplifying Advanced Deployments with Automation API&lt;/h2&gt;
&lt;p&gt;The challenges facing the Zephyr teams are not unique to Zephyr. Many IaC vendors recommend using a modular approach. Unfortunately, most IaC products rely on less-than-ideal approaches&amp;mdash;like Bash scripts or Makefiles&amp;mdash;to &amp;ldquo;stitch&amp;rdquo; together the different modules.&lt;/p&gt;
&lt;p&gt;In some cases, organizations can use existing build automation tools and CI/CD pipelines to assist. What&amp;rsquo;s really needed, though, is a way to programmatically control the IaC tooling, and that&amp;rsquo;s what Pulumi&amp;rsquo;s Automation API is. The Pulumi Automation API allows Zephyr&amp;rsquo;s teams to use a general purpose programming language to automate Pulumi operations like performing an update, refreshing the stack state, or destroying a stack. Like Pulumi itself, the Pulumi Automation API supports multiple languages and supports multiple state backends, including Pulumi Cloud. Further, there is &lt;strong&gt;no requirement&lt;/strong&gt; that Pulumi Automation API programs are written in the same language as the Pulumi programs they automate. In the case of Zephyr, which thus far has chosen to use TypeScript for their Pulumi programs, this means they are free to use any supported language for their Pulumi Automation API programs.&lt;/p&gt;
&lt;p&gt;When the Zephyr team started evaluating options for simplifying the deployment of their online store and associated infrastructure, they decided to move forward with the Pulumi Automation API for these reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tight integration with Pulumi and Pulumi Cloud&lt;/li&gt;
&lt;li&gt;Support for Pulumi Deployments (something we&amp;rsquo;ll examine in the next blog post)&lt;/li&gt;
&lt;li&gt;Support for a broad range of programming languages, just like Pulumi itself&lt;/li&gt;
&lt;li&gt;No requirement to use the same language as the Pulumi programs being automated (in fact, a Pulumi Automation API program could automate multiple programs written in different supported languages!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at Zephyr&amp;rsquo;s initial foray into using Pulumi Automation API.&lt;/p&gt;
&lt;h2 id="performing-stack-operations-efficiently"&gt;Performing Stack Operations Efficiently&lt;/h2&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;You can view the Pulumi Automation API program that Zephyr wrote in the &lt;code&gt;local-source&lt;/code&gt; directory of &lt;a href="https://github.com/pulumi/zephyr-automation/"&gt;the &lt;code&gt;pulumi/zephyr-automation&lt;/code&gt; repository&lt;/a&gt; on GitHub, in the &lt;code&gt;blog/automation-api&lt;/code&gt; branch.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Zephyr&amp;rsquo;s team decided to write their Pulumi Automation API program in Golang. One of the first things you&amp;rsquo;ll note about the Pulumi Automation API program is that it looks more like a &amp;ldquo;regular&amp;rdquo; Go program than a typical Pulumi Go program.&lt;/p&gt;
&lt;p&gt;For example, a typical Pulumi Automation API program written in Go starts out like any other Go program:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// All your Go code is here&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In a typical Pulumi Go program, on the other hand, the &lt;code&gt;main()&lt;/code&gt; function starts off with a &lt;code&gt;pulumi.Run&lt;/code&gt; call, like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// All the Pulumi Go code is here&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The easiest way to understand this difference is to consider the way each program is invoked. A Pulumi Automation API program written in Go is executed by running &lt;code&gt;go run main.go&lt;/code&gt;, like running any other Go program. In contrast, a Pulumi Go program is invoked by the &lt;code&gt;pulumi&lt;/code&gt; CLI.&lt;/p&gt;
&lt;p&gt;Zephyr&amp;rsquo;s Automation API program &lt;a href="https://github.com/pulumi/zephyr-automation/blob/blog/automation-api/local-source/main.go#L34-L40"&gt;starts out&lt;/a&gt; with checking for a command-line argument to indicate the desired mode of the operation&amp;mdash;should it be creating/updating resources, or destroying resources? In a standard Pulumi Go program, this would be determined by the invocation of the &lt;code&gt;pulumi&lt;/code&gt; CLI (for example, whether you ran &lt;code&gt;pulumi up&lt;/code&gt; or &lt;code&gt;pulumi destroy&lt;/code&gt;). Here, though, that falls to the user to check:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Determine mode of operation; default is to refresh/update&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;destroy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;argsWithoutProg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&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="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;argsWithoutProg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;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;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;argsWithoutProg&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="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;destroy&amp;#34;&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;destroy&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="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, the program &lt;a href="https://github.com/pulumi/zephyr-automation/blob/blog/automation-api/local-source/main.go#L42-L53"&gt;reads some configuration data&lt;/a&gt; from a YAML configuration file; this configuration data includes things like the relative file system location of the Pulumi programs that this Automation API program is going to execute:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Get configuration data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;config.yaml&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Error reading configuration file: %v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&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="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Environment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;err&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="nx"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Unmarshal&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="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Error parsing configuration information: %v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&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="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Separating configuration from code in this manner allows you to use the language&amp;rsquo;s existing ability to create binary executables that can be easily distributed to your teams. For example, Zephyr has the option of running &lt;code&gt;go build&lt;/code&gt; to create a binary executable for distribution to internal teams.&lt;/p&gt;
&lt;p&gt;From there, the Automation API program gets into &lt;a href="https://github.com/pulumi/zephyr-automation/blob/blog/automation-api/local-source/main.go#L58-L92"&gt;setting up the stacks&lt;/a&gt; it will use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Set up base stack&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;baseStackName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullyQualifiedStackName&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="nx"&gt;Organization&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &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;BaseProject&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="w"&gt; &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;StackName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;baseStack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UpsertStackLocalSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;baseStackName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &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;BaseProject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Failed to create or select %s stack: %v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &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;BaseProject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Nickname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&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="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Successfully created/selected %s stack\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &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;BaseProject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Nickname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In this particular program, Zephyr decided to start with a &lt;em&gt;local workspace.&lt;/em&gt; (You can read more about workspaces in the &lt;a href="https://www.pulumi.com/docs/using-pulumi/automation-api/getting-started-automation-api/#associate-with-a-stack"&gt;Pulumi Automation API docs&lt;/a&gt;.) This is reflected in the use of the &lt;code&gt;UpsertStackLocalSource&lt;/code&gt; method shown above, which will create or select a stack for a Pulumi program found in a local filesystem location.&lt;/p&gt;
&lt;p&gt;Users can also use &lt;code&gt;UpsertStackInlineSource&lt;/code&gt;, which embeds the Pulumi program into the Automation API program as a function. There are trade-offs to each approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using local source (i.e., the Pulumi program is stored outside of the Automation API program) has the benefit of allowing users to independently revise the Pulumi program or the Automation API program; this may be the ideal approach where organizations have separate teams that will create, manage, or maintain the Automation API programs. In Zephyr&amp;rsquo;s case, where there is a platform team, this made a lot of sense for them. This does also mean, as you might guess, that the Pulumi programs must be reachable by the Automation API program (this typically means users need to clone down the appropriate repositories to their system first).&lt;/li&gt;
&lt;li&gt;Using inline source (where the Pulumi program is embedded in the Automation API program) may be a better fit when there is a single team managing or maintaining both the Pulumi program(s) as well as the Automation API program(s), or when&amp;mdash;for whatever reason&amp;mdash;these programs should not be revised independently of one another. This has the benefit of removing the need for users to clone or copy separate Pulumi programs onto their system before being able to use the Automation API program.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After all four stacks&amp;mdash;base, platform, data, and app&amp;mdash;have been set up for use within the Pulumi Automation API program, then comes the &amp;ldquo;real work&amp;rdquo;: performing operations on those stacks. To reduce duplicate code, the Zephyr program makes use of three functions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;refreshStack&lt;/code&gt; function (starting on &lt;a href="https://github.com/pulumi/zephyr-automation/blob/blog/automation-api/local-source/main.go#L199-L216"&gt;line 199&lt;/a&gt; of &lt;code&gt;main.go&lt;/code&gt;) performs the equivalent of a &lt;code&gt;pulumi refresh&lt;/code&gt; against a stack.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;updateStack&lt;/code&gt; function (starting on &lt;a href="https://github.com/pulumi/zephyr-automation/blob/blog/automation-api/local-source/main.go#L218-L234"&gt;line 218&lt;/a&gt; of &lt;code&gt;main.go&lt;/code&gt;) performs an update against the stack. This is like running &lt;code&gt;pulumi up&lt;/code&gt; against a stack.&lt;/li&gt;
&lt;li&gt;Finally, the &lt;code&gt;deleteStack&lt;/code&gt; function (starting on &lt;a href="https://github.com/pulumi/zephyr-automation/blob/blog/automation-api/local-source/main.go#L236-L252"&gt;line 236&lt;/a&gt; of &lt;code&gt;main.go&lt;/code&gt;) destroys the resources in a stack. This equates to running &lt;code&gt;pulumi destroy&lt;/code&gt; against a stack.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Using these functions, &lt;a href="https://github.com/pulumi/zephyr-automation/blob/blog/automation-api/local-source/main.go#L94-L197"&gt;lines 94 through 197&lt;/a&gt; perform the key task of taking action to stand up or tear down the Zephyr online store and all associated infrastructure. If the user passes the parameter &amp;ldquo;destroy&amp;rdquo; on the command line (meaning the Pulumi Automation API program is invoked using &lt;code&gt;go run main.go destroy&lt;/code&gt;), then it will tear down the stacks. Otherwise, the Pulumi Automation API program will perform a refresh and then an update&amp;mdash;much the same way that the &lt;code&gt;pulumi&lt;/code&gt; CLI itself behaves.&lt;/p&gt;
&lt;p&gt;Along the way, you can see the Pulumi Automation API program passing configuration values to the stacks being orchestrated (this is &lt;a href="https://github.com/pulumi/zephyr-automation/blob/blog/automation-api/local-source/main.go#L177-L182"&gt;lines 177 through 182&lt;/a&gt;, for example):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Set config values for app stack&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;appStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;platformOrgName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ConfigValue&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="w"&gt; &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;Organization&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Secret&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="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;appStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;dataOrgName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ConfigValue&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="w"&gt; &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;Organization&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Secret&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="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;appStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;platformProjName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ConfigValue&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="w"&gt; &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;PlatformProject&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="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Secret&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="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;appStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;dataProjName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ConfigValue&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="w"&gt; &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;DataProject&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="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Secret&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="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;appStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;platformStackName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ConfigValue&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="w"&gt; &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;StackName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Secret&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="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;appStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;dataStackName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ConfigValue&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="w"&gt; &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;StackName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Secret&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="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Among other things, this means you can take your existing Pulumi programs, and&amp;mdash;without making any changes&amp;mdash;orchestrate them using an Automation API program! Further, in reviewing the code shown above, you can see that the Pulumi Automation API ties into &lt;a href="https://www.pulumi.com/docs/concepts/secrets/"&gt;Pulumi&amp;rsquo;s secrets functionality&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the end, using Pulumi&amp;rsquo;s Automation API enabled Zephyr&amp;rsquo;s platform team to simplify the workflow for standing up the online store application and all of its associated infrastructure into a simple and straightforward process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Developers ensure they have cloned the appropriate source repositories onto their system.&lt;/li&gt;
&lt;li&gt;Developers run &lt;code&gt;go run main.go&lt;/code&gt; (or &lt;code&gt;go run main.go destroy&lt;/code&gt;) to stand up or tear down the online store and all associated infrastructure.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;Although Zephyr uses multiple GitHub repositories, the Pulumi Automation API has no requirements for multiple repositories. You could use a monorepo, if that better suits your particular requirements.&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="summarizing-best-practices-automation-api"&gt;Summarizing Best Practices: Automation API&lt;/h2&gt;
&lt;p&gt;This post discussed the following best practices when using Pulumi Automation API:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use the Automation API to orchestrate operations on multiple stacks.&lt;/strong&gt; In cases where you&amp;rsquo;ve split Pulumi projects (per the recommendations in the post on &lt;a href="https://www.pulumi.com/blog/iac-best-practices-structuring-pulumi-projects/"&gt;structuring Pulumi projects&lt;/a&gt;), you&amp;rsquo;ve introduced a potential opportunity for using Automation API to orchestrate operations. This is a great counter-balance to offset the relative increase in complexity that comes from breaking large Pulumi projects into smaller, more focused Pulumi projects.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Separate configuration from code.&lt;/strong&gt; This is already a generally-accepted recommendation in most programming circles, and it applies to Automation API programs as well. Doing so enables you, if you wish, to distribute binary artifacts to teams instead of requiring them to execute the source code of your Automation API programs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use local source to decouple Automation API programs from Pulumi programs when separate teams are involved.&lt;/strong&gt; When separate teams are creating, managing, or maintaining the Automation API programs and the Pulumi programs, using local source instead of inline source enables the teams to operate somewhat independently.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;When there is a single team managing both Automation API programs and Pulumi programs, use inline source.&lt;/strong&gt; This is also true when, perhaps due to compliance or regulatory concerns, the Automation API program should be more tightly coupled to the Pulumi programs it is automating.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Zephyr series wraps up in the next post, where we summarize all our recommendations so far. We&amp;rsquo;ll also take a look at areas of potential future growth for the team at Zephyr. Don&amp;rsquo;t miss it!&lt;/p&gt;</description><author>Scott Lowe</author><category>best-practices</category><category>automation-api</category><category>go</category></item><item><title>IaC Best Practices: Implementing RBAC and Security</title><link>https://www.pulumi.com/blog/iac-best-practices-implementing-rbac-and-security/</link><pubDate>Tue, 23 May 2023 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/iac-best-practices-implementing-rbac-and-security/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/iac-best-practices-implementing-rbac-and-security/index.png" /&gt;
&lt;p&gt;This post continues our series of blog posts focused on IaC best practices. In earlier posts, we introduced Zephyr Archaeotech Emporium, the fictional company that sits at the center of this series, and discussed Zephyr&amp;rsquo;s primary use case for Pulumi: managing their online retail store. You read how Zephyr&amp;rsquo;s initial use of Pulumi changed to incorporate the use of short-lived per-developer stacks. Later, as Zephyr continued to grow, you saw how Zephyr restructured their Pulumi projects and stacks, and incorporated the use of Stack References. This post is a complement to the post on structuring Pulumi projects, concentrating on the use of role-based access control (RBAC) and security in Zephyr&amp;rsquo;s multi-project configuration.&lt;/p&gt;
&lt;p&gt;The ultimate goal of this series is to discuss best practices for using Pulumi to manage a fairly complex containerized application. You&amp;rsquo;ve seen these practices emerge over the course of the series&amp;mdash;not all immediately, and not all right away. Structuring the blog series in this way is a deliberate decision; many best practices are &amp;ldquo;point in time&amp;rdquo; recommendations: they are context-dependent and the recommendations for any given company, like Zephyr, may change as the company and its needs change.&lt;/p&gt;
&lt;p&gt;For ease of navigation, here are links to all the blog posts in the series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-understanding-code-organization-stacks/"&gt;IaC Best Practices: Understanding Code Organization and Stacks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-enabling-developer-stacks-git-branches/"&gt;IaC Best Practices: Enabling Developer Stacks and Git Branches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-structuring-pulumi-projects/"&gt;IaC Best Practices: Structuring Pulumi Projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-applying-stack-references/"&gt;IaC Best Practices: Applying Stack References&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IaC Best Practices: Implementing RBAC and Security (the post you&amp;rsquo;re reading)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-using-automation-api/"&gt;IaC Best Practices: Using Automation API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-summarizing-key-learnings"&gt;IaC Best Practices: Summarizing Key Learnings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="evolving-project-structure-to-enhance-security-and-scalability"&gt;Evolving Project Structure to Enhance Security and Scalability&lt;/h2&gt;
&lt;p&gt;So far, you&amp;rsquo;ve read about how Zephyr started out with a single project and a single Git repository. Adding short-lived per-developer stacks, as described in the post on &lt;a href="https://www.pulumi.com/blog/iac-best-practices-enabling-developer-stacks-git-branches/"&gt;best practices for developer stacks and Git branches&lt;/a&gt;, didn&amp;rsquo;t really impact this structure. However, as the company grew, Zephyr&amp;rsquo;s project and Git repository structure evolved into its current form:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &amp;ldquo;zephyr-infra&amp;rdquo; project handles base infrastructure (VPC and related constructs).&lt;/li&gt;
&lt;li&gt;The &amp;ldquo;zephyr-k8s&amp;rdquo; project automates the Kubernetes platform layer, building atop the base infrastructure components supplied by the &amp;ldquo;zephyr-infra&amp;rdquo; project.&lt;/li&gt;
&lt;li&gt;Finally, the &amp;ldquo;zephyr-app&amp;rdquo; project handles the deployment of the online retail store application onto Kubernetes, layering on top of the platform provided by the &amp;ldquo;zephyr-k8s&amp;rdquo; project.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can review the reasons for this structure in the post on &lt;a href="https://www.pulumi.com/blog/iac-best-practices-structuring-pulumi-projects/"&gt;best practices for structuring Pulumi projects&lt;/a&gt;. One of the reasons listed in the third Zephyr blog post was security:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;hellip;security is a factor that affects how projects and stacks should be structured. In Zephyr&amp;rsquo;s case, the ability for one team to affect another team&amp;rsquo;s resources was present when everything was a single project. With multiple projects, Zephyr can use stack permissions and role-based access control (RBAC) in &lt;a href="https://www.pulumi.com/product/pulumi-cloud/"&gt;Pulumi Cloud&lt;/a&gt; to appropriately control &lt;em&gt;who&lt;/em&gt; can affect &lt;em&gt;what&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The last part of that quote&amp;mdash;the part about being able to &amp;ldquo;appropriately control &lt;em&gt;who&lt;/em&gt; can affect &lt;em&gt;what&lt;/em&gt;&amp;rdquo;&amp;mdash;is another way of describing what&amp;rsquo;s known as &lt;a href="https://en.wikipedia.org/wiki/Principle_of_least_privilege"&gt;the principle of least privilege&lt;/a&gt;. If you&amp;rsquo;re unfamiliar with this term, we recommend following the link and getting more information, because the principle of least privilege stands at the core of all the recommended practices you&amp;rsquo;ll read in this post.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;Pulumi supports a number of self-managed backends, including Amazon S3 (and S3-compatible servers like Minio or Ceph), Azure Blob Storage, and Google Cloud Storage. The RBAC functionality described in this post applies only to the managed Pulumi Cloud backend.&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="understanding-the-rbac-building-blocks-in-pulumi-cloud"&gt;Understanding the RBAC Building Blocks in Pulumi Cloud&lt;/h2&gt;
&lt;p&gt;To understand how to secure your Pulumi projects and stacks in Pulumi Cloud, it is important to first explain the basic building blocks you&amp;rsquo;ll use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Organization:&lt;/em&gt; An organization represents a customer/company within Pulumi Cloud. Generally speaking, a single company like Zephyr is represented by a single organization.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Member:&lt;/em&gt; Each member represents a user account that is part of an organization. In Zephyr&amp;rsquo;s case, each Zephyr employee that needs to interact with Pulumi is a member of Zephyr&amp;rsquo;s organization in Pulumi Cloud.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Team:&lt;/em&gt; A team is a grouping of members, and is the primary component involved in assigning per-stack permissions in the &lt;a href="https://www.pulumi.com/docs/pulumi-cloud/access-management/teams/"&gt;Pulumi Cloud RBAC model&lt;/a&gt;. How you structure teams within your organization will depend on a great many different factors we&amp;rsquo;ll explore in this post.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Stack:&lt;/em&gt; A stack is a separate instance of the resources created by a Pulumi program within a project. Each stack has its own independent state and its own configuration values. With respect to RBAC, each stack is a separate entity to which one or more teams can be granted permission. In Zephyr&amp;rsquo;s case, there is a long-lived production stack for each of the separate projects, and a persistent (or long-lived) test stack for each of the separate projects. There are also the per-developer stacks, which are typically short-lived.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This diagram, also used in the post on &lt;a href="https://www.pulumi.com/blog/iac-best-practices-applying-stack-references/"&gt;best practices for stack references&lt;/a&gt;, shows the stacks and projects for Zephyr&amp;rsquo;s online store:&lt;/p&gt;
&lt;p&gt;&lt;img src="gh-repos-pulumi-projects-stacks.jpg" alt="A diagram depicting how Zephyr&amp;rsquo;s projects and stacks are structured"&gt;&lt;/p&gt;
&lt;p&gt;Now, how are these different components assembled together? At its most basic level, you would use teams to group members together, and then assign stack permissions to the team. This diagram helps illustrate the idea:&lt;/p&gt;
&lt;p&gt;&lt;img src="security-stacks-teams.jpg" alt="A diagram illustrating the relationship between stacks, teams, and members"&gt;&lt;/p&gt;
&lt;p&gt;A couple of important points emerge from the above diagram:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The relationship between stacks and teams is &lt;em&gt;many-to-many&lt;/em&gt;. That is, any given stack can have many teams assigned permissions, and any given team may be assigned permissions to many stacks.&lt;/li&gt;
&lt;li&gt;Similarly, the relationship between teams and members is also &lt;em&gt;many-to-many&lt;/em&gt; (any given team can&amp;mdash;and almost always will&amp;mdash;have multiple members, and any given member can be part of multiple teams at the same time).&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="assembling-security-for-stacks"&gt;Assembling Security for Stacks&lt;/h2&gt;
&lt;p&gt;The first, and perhaps the most important, best practice with regards to securing your stacks is &lt;strong&gt;to actually use Pulumi Cloud&amp;rsquo;s RBAC functionality&lt;/strong&gt;. While this may seem obvious, there are practical implications to this statement that are useful to explore.&lt;/p&gt;
&lt;p&gt;Pulumi Cloud has the ability to set a &lt;em&gt;default stack permission&lt;/em&gt; for all members of an organization. In the screenshot below, you can see that the default permission has been set to Write:&lt;/p&gt;
&lt;p&gt;&lt;img src="pulumi-cloud-stack-permissions.jpg" alt="Screenshot showing the default stack permissions for all members"&gt;&lt;/p&gt;
&lt;p&gt;In this configuration, you are effectively disabling Pulumi Cloud&amp;rsquo;s RBAC functionality. Permissions in Pulumi Cloud are &lt;em&gt;additive&lt;/em&gt;; that is, permissions only state what you can do, not what you can&amp;rsquo;t do. Therefore, if the default stack permission is set to Write, then assigning a team read-only permissions to a stack has no effect&amp;mdash;because members of that team can read from the stack (by virtue of their inclusion in a team) &lt;em&gt;and&lt;/em&gt; can write to the stack (by virtue of the default stack permission). The team members&amp;rsquo; read-only permission is &lt;em&gt;added&lt;/em&gt; to the default stack permission for their effective permission.&lt;/p&gt;
&lt;p&gt;For this reason, Pulumi recommends &lt;strong&gt;setting the default stack permission to Read or None.&lt;/strong&gt; Setting it to Read means that all members of the organization can see and read from stacks that aren&amp;rsquo;t their own, but can&amp;rsquo;t change or modify the others stacks. Setting it to None means that members have permissions to their own stacks by default&amp;mdash;gaining any form of access to stacks other than their own requires being added to a team that has permissions to those stacks.&lt;/p&gt;
&lt;p&gt;When it comes to assigning a team access to a stack, there are three options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stack reader (read-only access; also called &amp;ldquo;Stack reader access&amp;rdquo; in some areas of the Pulumi Cloud UI)&lt;/li&gt;
&lt;li&gt;Stack editor (read/write access, but can&amp;rsquo;t delete the stack; also called &amp;ldquo;Stack write access&amp;rdquo; in the Pulumi Cloud UI)&lt;/li&gt;
&lt;li&gt;Stack admin (full access; you may also see it called &amp;ldquo;Stack admin access&amp;rdquo;)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;When the &amp;ldquo;Allow organization members to create stacks and transfer stacks to this organization&amp;rdquo; is checked, like in the screenshot above, then the user (organization member) that creates a stack automatically becomes the stack admin for that stack. The user&amp;rsquo;s ability to delete the stack, however, is affected by the &amp;ldquo;Allow stack admins to delete stacks&amp;rdquo; setting.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Full details on what each of these permissions grants is detailed in &lt;a href="https://www.pulumi.com/docs/pulumi-cloud/projects-and-stacks/"&gt;the projects and stacks documentation&lt;/a&gt; (see the &amp;ldquo;Stack Permissions&amp;rdquo; section). In accordance with the principle of least privilege, Pulumi recommends you &lt;strong&gt;grant the minimum level of access necessary&lt;/strong&gt; in order for a user (organization member) to meet their job responsibilities. You accomplish this by adding the member to a team, and then granting the team the minimum level of necessary access. &lt;em&gt;You can&amp;rsquo;t assign stack permissions directly to users.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Because team membership is central to the use of stack permissions, Pulumi has several ways to make it easier to manage the relationship between teams and stacks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You can use the Pulumi Cloud user interface to assign a team access to a stack.&lt;/li&gt;
&lt;li&gt;You can use Pulumi Cloud&amp;rsquo;s REST API to control team access to stacks.&lt;/li&gt;
&lt;li&gt;You can use the Pulumi Cloud provider for Pulumi to programmatically manage team access to a stack using Pulumi itself.&lt;/li&gt;
&lt;li&gt;You can use the &lt;code&gt;--teams&lt;/code&gt; flag to &lt;code&gt;pulumi stack init&lt;/code&gt; to assign stack editor access to one or more teams when you create the stack.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In general, Pulumi recommends &lt;strong&gt;declaratively managing your Pulumi Cloud RBAC configuration&lt;/strong&gt; with the Pulumi Cloud provider (formerly known as the Pulumi Service provider), unless you have specific requirements that would prevent it. However, be aware that the Pulumi Cloud provider doesn&amp;rsquo;t allow you to change a member&amp;rsquo;s role in an organization (every member is either a &amp;ldquo;member&amp;rdquo; or an &amp;ldquo;admin&amp;rdquo; within an organization); that would have to be done via the Pulumi Cloud user interface or REST API.&lt;/p&gt;
&lt;p&gt;Pulumi also recommends &lt;strong&gt;using the &lt;code&gt;--teams&lt;/code&gt; flag with &lt;code&gt;pulumi stack init&lt;/code&gt; to ensure that new stacks are properly assigned team permissions upon creation.&lt;/strong&gt; You can then go back and fine-tune those team permissions&amp;mdash;which are set to &amp;ldquo;Stack editor&amp;rdquo; or &amp;ldquo;Stack write access&amp;rdquo; when you use the &lt;code&gt;--teams&lt;/code&gt; flag&amp;mdash;using a Pulumi program with the Pulumi Cloud provider. Alternately, teams can use a team access token when creating a new stack, and that will also grant the team permission to the newly-created stack.&lt;/p&gt;
&lt;p&gt;Finally, Pulumi recommends &lt;strong&gt;allowing users to create their own stacks.&lt;/strong&gt; Otherwise, only an organization admin would be allowed to create new stacks, and this is likely to cause a bottleneck in provisioning cloud infrastructure&amp;mdash;one of the very issues that infrastructure as code tools like Pulumi are attempting to solve! (It may be necessary in some cases to restrict this functionality, such as in certain highly regulated industries or verticals.) Preventing users from creating their own stacks would also prevent the use of per-developer stacks like Zephyr uses, which as you&amp;rsquo;ve seen can be enormously helpful in accelerating development and deployment velocity.&lt;/p&gt;
&lt;h2 id="inspecting-rbac-implementation"&gt;Inspecting RBAC Implementation&lt;/h2&gt;
&lt;p&gt;Zephyr&amp;rsquo;s RBAC implementation in Pulumi Cloud is represented in part by a diagram that you also saw earlier in this post:&lt;/p&gt;
&lt;p&gt;&lt;img src="security-stacks-teams.jpg" alt="A diagram illustrating how Zephyr set up role-based access control to their Pulumi stacks"&gt;&lt;/p&gt;
&lt;p&gt;Zephyr set the organization default stack permission to Read. This enabled teams to read all stacks, and enabled the continued use of stack references without any additional configuration. Teams were created to grant &amp;ldquo;Stack editor&amp;rdquo; (also called &amp;ldquo;Stack write access&amp;rdquo;) to stacks based on the overall organizational structure of the company. Thus, the Platform team gained access to the non-production &lt;code&gt;zephyr-infra&lt;/code&gt; and &lt;code&gt;zephyr-k8s&lt;/code&gt; stacks, while the Application team was given access to the non-production &lt;code&gt;zephyr-app&lt;/code&gt; stacks. However, only members of the Production team were given access to the production stacks in all three projects, ensuring that access to update those stacks&amp;mdash;and thus the actual resources running Zephyr&amp;rsquo;s online store&amp;mdash;remained relatively controlled.&lt;/p&gt;
&lt;h2 id="summarizing-best-practices-rbac--security"&gt;Summarizing Best Practices: RBAC &amp;amp; Security&lt;/h2&gt;
&lt;p&gt;The post covered the following guidelines for securing your stacks in Pulumi Cloud:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use Pulumi Cloud&amp;rsquo;s RBAC functionality by setting your organization&amp;rsquo;s default stack permissions to Read or None.&lt;/strong&gt; This is generally the safest way to implement the principle of least privilege, unless you have a good use case for allowing all organization members full write access to all stacks.&lt;/li&gt;
&lt;li&gt;When assigning team permissions, &lt;strong&gt;grant the minimum level of access necessary.&lt;/strong&gt; If a user isn&amp;rsquo;t responsible for making updates to a stack, then give that user stack reader permission through a team membership (remember that you can&amp;rsquo;t assign stack permissions directly to organization members, you have to use a team). Keep in mind, though: if the organization level stack permission has been set to Read, then members already have the equivalent of stack reader permissions on all stacks, so creating a team to grant stack reader permission is unnecessary and superfluous.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Declaratively manage your Pulumi Cloud RBAC configuration&lt;/strong&gt; where possible using the Pulumi Cloud provider from within a Pulumi program.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use the &lt;code&gt;--teams&lt;/code&gt; flag with &lt;code&gt;pulumi stack init&lt;/code&gt; to ensure that teams have stack permissions upon stack creation.&lt;/strong&gt; You can also use a team access token to accomplish the same result.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Allow users to create their own stacks.&lt;/strong&gt; Generally, turning this functionality off (found in the Settings &amp;gt; Access Management screen of Pulumi Cloud) will end up creating a bottleneck, as only organization admins would be allowed to create new stacks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the next IaC Best Practices post, the focus is on &lt;a href="https://www.pulumi.com/automation/"&gt;Automation API&lt;/a&gt;. Stay tuned to see how Zephyr uses Automation API to further streamline and automate the deployment of their cloud resources!&lt;/p&gt;</description><author>Scott Lowe</author><category>best-practices</category><category>rbac</category><category>security</category></item><item><title>IaC Best Practices: Applying Stack References</title><link>https://www.pulumi.com/blog/iac-best-practices-applying-stack-references/</link><pubDate>Fri, 31 Mar 2023 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/iac-best-practices-applying-stack-references/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/iac-best-practices-applying-stack-references/index.png" /&gt;
&lt;p&gt;This is the fourth post in a series of blog posts focused on Zephyr Archaeotech Emporium&amp;mdash;our fictional company&amp;mdash;and their use of Pulumi to manage their online retail store. In the first three posts, you saw how Zephyr&amp;rsquo;s initial use of Pulumi changed as the company grew, and how the use of short-lived per-developer stacks helped Zephyr&amp;rsquo;s application development team meet the demands of a fast-growing company. This post is a complement to the earlier post on structuring Pulumi projects, discussing how Zephyr uses &lt;a href="https://www.pulumi.com/tutorials/building-with-pulumi/stack-references/"&gt;Stack References&lt;/a&gt; to link their projects together and sharing some IaC best practices around the use of Stack References.&lt;/p&gt;
&lt;p&gt;As you may have read in earlier Zephyr posts, the ultimate goal of the Zephyr series is to share best practices on the use of Pulumi to manage your infrastructure and application resources (using a fictional company and a somewhat complex containerized application as the use case). The series exposes those practices over time&amp;mdash;not all right away, and not without also discussing the context for the recommendations. This is deliberate, demonstrating how &amp;ldquo;point-in-time&amp;rdquo; recommendations change based on the needs of the company and its requirements.&lt;/p&gt;
&lt;p&gt;Here are links to all the blog posts in the series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-understanding-code-organization-stacks/"&gt;IaC Best Practices: Understanding Code Organization and Stacks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-enabling-developer-stacks-git-branches/"&gt;IaC Best Practices: Enabling Developer Stacks and Git Branches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-structuring-pulumi-projects/"&gt;IaC Best Practices: Structuring Pulumi Projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IaC Best Practices: Applying Stack References&lt;/strong&gt; (you are here)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-implementing-rbac-and-security/"&gt;IaC Best Practices: Implementing RBAC and Security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-using-automation-api/"&gt;IaC Best Practices: Using Automation API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-summarizing-key-learnings"&gt;IaC Best Practices: Summarizing Key Learnings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="reviewing-zephyrs-current-status"&gt;Reviewing Zephyr&amp;rsquo;s Current Status&lt;/h2&gt;
&lt;p&gt;This post continues the previous post in the series. Zephyr&amp;rsquo;s infrastructure footprint and Pulumi codebase is the same; that is, Zephyr now has &lt;em&gt;three&lt;/em&gt; Pulumi projects (zephyr-infra, zephyr-k8s, and zephyr-app). Each of these projects handles a different aspect of Zephyr&amp;rsquo;s online store:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &amp;ldquo;zephyr-infra&amp;rdquo; project handles base infrastructure (VPC and related constructs).&lt;/li&gt;
&lt;li&gt;The &amp;ldquo;zephyr-k8s&amp;rdquo; project handles the Kubernetes platform layer, building atop the infrastructure components provided by the &amp;ldquo;zephyr-infra&amp;rdquo; project.&lt;/li&gt;
&lt;li&gt;The &amp;ldquo;zephyr-app&amp;rdquo; project handles the deployment of the online store services onto Kubernetes, layering on top of the platform provided by the &amp;ldquo;zephyr-k8s&amp;rdquo; project.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The reasons for this structure are explained in &lt;a href="https://www.pulumi.com/blog/iac-best-practices-structuring-pulumi-projects/"&gt;structuring Pulumi projects&lt;/a&gt; post. For Zephyr, it came down to supporting multiple teams and allowing the application to evolve independently of the infrastructure that supports it.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;You can continue to use the &lt;code&gt;blog/multi-project&lt;/code&gt; branch in the GitHub repositories to see the state of Zephyr&amp;rsquo;s code and projects for this blog post.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;It&amp;rsquo;s clear that there are dependencies across projects: The &lt;code&gt;zephyr-k8s&lt;/code&gt; project needs to know the VPC ID and the subnet IDs from the &lt;code&gt;zephyr-infra&lt;/code&gt; project. Similarly, the &lt;code&gt;zephyr-app&lt;/code&gt; project needs to have access to the &lt;a href="https://www.pulumi.com/templates/kubernetes/"&gt;Kubernetes cluster&lt;/a&gt; details from the &lt;code&gt;zephyr-k8s&lt;/code&gt; project. As the Zephyr team went about building the code that&amp;rsquo;s now in use in their multi-project architecture, they needed to decide how best to handle these cross-project dependencies. The Zephyr team knew that hard-coding output values from one project as configuration values in another stack wasn&amp;rsquo;t the ideal way (and in fact Pulumi &lt;strong&gt;strongly&lt;/strong&gt; recommends against hard-coding output values), but what was the best solution to use? The answer to that question is &lt;em&gt;stack references&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="linking-stacks-with-stack-references"&gt;Linking Stacks with Stack References&lt;/h2&gt;
&lt;p&gt;Stack references allow you to access output values from one stack in another stack. In other words, stack references offer a way to programmatically &amp;ldquo;link&amp;rdquo; stacks in different projects for the purpose of sharing information. You can find more details &lt;a href="https://www.pulumi.com/docs/concepts/stack/#stackreferences"&gt;in the documentation on stack references&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using stack references, the Zephyr team can make the necessary connections they need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The code handling the Kubernetes cluster can reference the VPC ID and subnet IDs from the base infrastructure layer.&lt;/li&gt;
&lt;li&gt;Similarly, the code that is responsible for deploying the online store services can reference the necessary Kubernetes connection details (the Kubeconfig) for the Kubernetes cluster.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can see how the Zephyr team did this for their projects, including code samples, toward the end of this post (in the section titled &amp;ldquo;Examining Zephyr&amp;rsquo;s use of stack references&amp;rdquo;). This graphical representation also illustrates Zephyr&amp;rsquo;s current repository and project structure as well as the stack references:&lt;/p&gt;
&lt;p&gt;&lt;img src="gh-repos-pulumi-projects-stacks.jpg" alt="Graphic showing the relationships between three Git repositories and three Pulumi projects"&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s worthwhile to note that any information that needs to be accessible from another stack via a stack reference must be exported as a stack output in the source stack. If you don&amp;rsquo;t mark it as a stack output, then it can&amp;rsquo;t be used in a stack reference. Adding stack outputs after the fact requires little effort and has no impact on existing infrastructure.&lt;/p&gt;
&lt;p&gt;Also, any value retrieved via a stack reference is treated as &lt;a href="https://www.pulumi.com/docs/concepts/inputs-outputs/"&gt;a Pulumi Output&lt;/a&gt;, and therefore may require some extra work to transform values (such as the use of &lt;code&gt;Output.apply&lt;/code&gt;). The recent addition of &lt;code&gt;OutputDetails&lt;/code&gt; support in Pulumi&amp;mdash;you can read more about &lt;code&gt;OutputDetails&lt;/code&gt; &lt;a href="https://www.pulumi.com/blog/stack-reference-output-details/"&gt;in this blog post announcing the functionality&lt;/a&gt;&amp;mdash;helps considerably in this situation. Some SDKs also have language-specific mechanisms that can help; for example, using Go&amp;rsquo;s &lt;code&gt;.AsStringArrayOutput()&lt;/code&gt; method on a &lt;code&gt;StackReference.GetOutput&lt;/code&gt; statement makes referencing subnet IDs from other project much easier.&lt;/p&gt;
&lt;p&gt;While stack references are conceptually straightforward and not difficult to implement, there are some best practices to be mindful of regarding the use of stack outputs and stack references:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When it comes to stack outputs, &lt;strong&gt;export what is needed.&lt;/strong&gt; If it needs to be accessed from outside the stack, export it; otherwise, don&amp;rsquo;t. Why? Stack outputs form the application programming interface (API) by which other Pulumi programs interact with this Pulumi program. Like in any API implementation, expose what the user cares about or is likely to need, and leave the rest as an implementation detail. You can add more stack outputs after the fact with only minimal code changes, and as has been mentioned already this is done with a quick &lt;code&gt;pulumi up&lt;/code&gt; that has no affect on existing infrastructure (you&amp;rsquo;re modifying the stack object itself).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Be judicious in your use of stack references.&lt;/strong&gt; This is sort of the &amp;ldquo;flip side&amp;rdquo; of the previous bullet. If you need to reference an output from another stack, then do so; otherwise, don&amp;rsquo;t. If you find yourself needing lots of stack references, then the way in which you&amp;rsquo;ve structured your projects may need to be re-examined so as to minimize the coupling between projects. Additionally, there is a potential &amp;ldquo;speed of light&amp;rdquo; concern here; if where you are executing your Pulumi code is across a high-latency link from where you store your Pulumi state, stack references will be affected by that latency.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If you need to expose lots of outputs on a stack, consider exporting a structured data object.&lt;/strong&gt; It&amp;rsquo;s possible to construct a JSON object (or dict or struct, depending on your language) to hold all the stack outputs, and then export that object. Then a stack reference can read that object, resulting in a single call to the backend. Be aware, though, that you&amp;rsquo;ll need to write the necessary code to understand/import/unmarshall that JSON object in the referring stack, so there is a small amount of extra work required in this situation.&lt;/li&gt;
&lt;li&gt;In line with parameterizing as much of your code as possible, &lt;strong&gt;also be sure to parameterize your stack references.&lt;/strong&gt; A stack reference is built using an organization name, a project name, and a stack name. Don&amp;rsquo;t hardcode these values; instead, use configuration values to allow the users to specify from which source stack(s) the values will be referenced. You&amp;rsquo;ll see an example of that in the next section.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plan for handling cascading stack updates.&lt;/strong&gt; If a stack has outputs being consumed via stack references, what happens when those stack outputs change? The Pulumi Service &lt;a href="https://www.pulumi.com/docs/pulumi-cloud/webhooks/"&gt;supports webhooks&lt;/a&gt; that can help, and Pulumi is currently exploring options for having Pulumi Deployments help with this (see &lt;a href="https://github.com/pulumi/pulumi-cloud-requests/issues/181"&gt;Pulumi Cloud Requests GitHub issue&lt;/a&gt;). In practice this should be uncommon, and frequent occurrences may be a good indicator that your resource partitioning (how resources are split among multiple projects) needs to be adjusted.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One thing not listed above is security&amp;mdash;and that&amp;rsquo;s because a later post in the series focuses on this topic. You&amp;rsquo;ll want to keep the security of your stacks in mind when planning for the use of stack references. The teams managing the stacks where the stack references exist will need at least read-only access to the stacks where the outputs exist. (For more on this front, refer to &lt;a href="https://www.pulumi.com/blog/iac-best-practices-implementing-rbac-and-security/"&gt;the IaC Best Practices post on RBAC and security&lt;/a&gt;.)&lt;/p&gt;
&lt;h2 id="examining-use-of-stack-references"&gt;Examining Use of Stack References&lt;/h2&gt;
&lt;p&gt;With these best practices in mind, you can examine the Zephyr team&amp;rsquo;s implementation to see how they put these recommendations into action.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In the code for the base infrastructure stack, you can see that Zephyr &lt;a href="https://github.com/pulumi/zephyr-infra/blob/blog/multi-project/index.ts#L14-L17"&gt;exported the essential values&lt;/a&gt; needed by the Kubernetes platform stack:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Export some values for use elsewhere
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vpcId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;eksVpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpcId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;privSubnetIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;eksVpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;privateSubnetIds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pubSubnetIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;eksVpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicSubnetIds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the Kubernetes platform stack, the Zephyr team &lt;a href="https://github.com/pulumi/zephyr-k8s/blob/blog/multi-project/index.ts#L10-L12"&gt;parameterized the values&lt;/a&gt; needed for the stack reference. This particular approach, by the way, is key to preserving &lt;a href="https://www.pulumi.com/blog/iac-best-practices-enabling-developer-stacks-git-branches/"&gt;the per-developer stacks&lt;/a&gt; to which the Zephyr team has grown accustomed (each developer needs to specify the correct organization, project, and stack name):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Grab some configuration values
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Some code omitted here
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;infraOrgName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;infraOrgName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;infraProjName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;infraProjName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;infraStackName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;infraStackName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Create a StackReference to get information from base stack
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;infraSr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StackReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;infraOrgName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;infraProjName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;infraStackName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As with the base infrastructure stack, &lt;a href="https://github.com/pulumi/zephyr-k8s/blob/blog/multi-project/index.ts#L36-L37"&gt;only the key value needed by other stacks&lt;/a&gt; is exported (in this case, the Kubeconfig required to access this cluster):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Export some values for use elsewhere
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;kubeconfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;eksCluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kubeconfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, in the application stack, the &lt;a href="https://github.com/pulumi/zephyr-app/blob/blog/multi-project/infra/index.ts#L7-L9"&gt;stack reference values are again parameterized&lt;/a&gt;, and the Kubeconfig&amp;mdash;necessary for the application stack to deploy onto the provisioned Kubernetes cluster&amp;mdash;is &lt;a href="https://github.com/pulumi/zephyr-app/blob/blog/multi-project/infra/index.ts#L11-L13"&gt;referenced via a stack reference&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Grab some configuration values
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;pulumi&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;k8sOrgName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;k8sOrgName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;k8sProjName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;k8sProjName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;k8sStackName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;k8sStackName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Create a StackReference to get Kubeconfig from base stack
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;kubeSr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StackReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;k8sOrgName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;k8sProjName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;k8sStackName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;All of the GitHub links in the paragraphs above reference the &lt;code&gt;blog/multi-project&lt;/code&gt; branch in each repository, which has the code as of this blog post (and the earlier blog posts).&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="summarizing-best-practices-stack-references"&gt;Summarizing Best Practices: Stack References&lt;/h2&gt;
&lt;p&gt;This post covered the following guidelines for working with Pulumi:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Export the minimum of what&amp;rsquo;s needed&lt;/strong&gt; from your stacks. It&amp;rsquo;s not difficult to add stack outputs later, and you can do it with no impact to existing infrastructure.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Be judicious in the use of stack references.&lt;/strong&gt; Reducing stack references where possible helps keep projects from becoming overly coupled together, much in the same way as minimizing stack outputs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use a structured data object for large numbers of values that need to be referenced.&lt;/strong&gt; This enables Pulumi to retrieve all the data with a single call, but be aware you&amp;rsquo;ll need to write extra code to understand the data structure in the referring stack.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parameterize your stack references.&lt;/strong&gt; Don&amp;rsquo;t hardcode organization, project, or stack name values. Instead, pass these in as configuration values (with default values applied, where applicable).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plan to build mechanisms to handle cascading stack updates.&lt;/strong&gt; Using the Pulumi Service&amp;rsquo;s webhook functionality can be useful here. Frequent instances of cascading updates might be an indicator that your project structure needs to be revisited for optimal resource partitioning.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The next post in the IaC best practices series focuses on security and role-based access control (RBAC)&amp;mdash;a topic you won&amp;rsquo;t want to miss! Before then, the next visit to the &amp;ldquo;Zephyr universe&amp;rdquo; will be to take a look at an oft-overlooked use case for Pulumi: local testing. Come see how to use Pulumi to automate local testing for development!&lt;/p&gt;</description><author>Scott Lowe</author><category>best-practices</category><category>cloud-engineering</category><category>kubernetes</category></item><item><title>IaC Best Practices: Structuring Pulumi Projects</title><link>https://www.pulumi.com/blog/iac-best-practices-structuring-pulumi-projects/</link><pubDate>Fri, 17 Mar 2023 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/iac-best-practices-structuring-pulumi-projects/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/iac-best-practices-structuring-pulumi-projects/index.png" /&gt;
&lt;p&gt;This is the third post in a series of blog posts focused on Zephyr Archaeotech Emporium&amp;mdash;our fictional company&amp;mdash;and their use of Pulumi to manage their online retail store. In the first post, you saw how Zephyr initially decided to go with a single &lt;a href="https://www.pulumi.com/docs/iac/concepts/projects/"&gt;Pulumi project&lt;/a&gt; for managing deployments of their online retail store application. In this post, you&amp;rsquo;ll see how Zephyr&amp;rsquo;s use of Pulumi changes as their company grows and evolves.&lt;/p&gt;
&lt;p&gt;The first post about &lt;a href="https://www.pulumi.com/blog/iac-best-practices-understanding-code-organization-stacks/"&gt;organizing code and stacks&lt;/a&gt; specifically mentions that the series will show &amp;ldquo;how Zephyr&amp;rsquo;s use of Pulumi evolves as the company grows and their online retail store application changes to accommodate their growth.&amp;rdquo; While the ultimate goal of this series is to discuss best practices for using Pulumi to manage a fairly complex containerized application, it&amp;rsquo;s important to note that many best practices are &amp;ldquo;point in time&amp;rdquo; recommendations: best practices are context-dependent and the recommended practices for a given organization will change as the company and its infrastructure needs also change. This blog post is an example of that evolution in action: the practices that worked well for Zephyr while they were smaller and had smaller infrastructure and less teams working on that infrastructure are no longer ideal as the company has grown.&lt;/p&gt;
&lt;p&gt;Here are links to all the blog posts in the series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-understanding-code-organization-stacks/"&gt;IaC Best Practices: Code Organization and Stacks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-enabling-developer-stacks-git-branches/"&gt;IaC Best Practices: Enabling Developer Stacks and Git Branches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IaC Best Practices: Structuring Pulumi Projects&lt;/strong&gt; (this post)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-applying-stack-references/"&gt;IaC Best Practices: Applying Stack References&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-implementing-rbac-and-security/"&gt;IaC Best Practices: Implementing RBAC and Security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-using-automation-api/"&gt;IaC Best Practices: Using Automation API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-summarizing-key-learnings"&gt;IaC Best Practices: Summarizing Key Learnings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="growing-development-teams"&gt;Growing Development Teams&lt;/h2&gt;
&lt;p&gt;As a result of launching its online presence, Zephyr saw amazing success. The market for arcane artifacts and novel curiosities is booming, and Zephyr is becoming a leader in this market. Funded by this success, Zephyr has grown rapidly. Along with this growth, Zephyr&amp;rsquo;s software engineering and IT teams also grew and expanded. Fortunately, Zephyr&amp;rsquo;s &lt;a href="https://www.pulumi.com/blog/iac-best-practices-enabling-developer-stacks-git-branches/"&gt;use of per-developer stacks with short-lived ephemeral Git branches&lt;/a&gt; positioned the development teams well for this growth.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;For a look at the state of Zephyr&amp;rsquo;s Pulumi code as it stood as of the end of the second post in the series, see the &lt;a href="https://github.com/pulumi/zephyr-app/tree/blog/dev-stacks"&gt;&lt;code&gt;blog/dev-stacks&lt;/code&gt;&lt;/a&gt; branch of the &lt;a href="https://github.com/pulumi/zephyr-app"&gt;&lt;code&gt;pulumi/zephyr-app&lt;/code&gt;&lt;/a&gt; repository on GitHub.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Naturally this growth has resulted in some separation of duties. The IT team now has responsibility for the infrastructure that supports the online retail store. Within that team, some team members are focused on &amp;ldquo;core&amp;rdquo; infrastructure, while other team members are focused only on &lt;a href="https://kubernetes.io/docs/concepts/security/multi-tenancy/"&gt;Kubernetes&lt;/a&gt; (a natural decision given Zephyr&amp;rsquo;s use of Kubernetes in their architecture). The application team continues to remain responsible for the online store application itself.&lt;/p&gt;
&lt;p&gt;This left Zephyr again asking some questions about the best path forward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Should they use a single project, or multiple projects?&lt;/strong&gt; Zephyr had used a single Pulumi project up until now, but growth and organizational change meant they wanted to make sure they were using Pulumi in a way that best reflected their needs. What are the advantages and disadvantages of each approach?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If multiple projects, how many projects?&lt;/strong&gt; What&amp;rsquo;s the best way to determine how many projects are needed? What factors play a role in this decision?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="structuring-pulumi-projects"&gt;Structuring Pulumi Projects&lt;/h2&gt;
&lt;p&gt;One project, or multiple projects? And if multiple projects, how many projects? These are the core questions the Zephyr needed to resolve. In the end, there are a number of key factors that determine the answers to these questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use case:&lt;/strong&gt; Zephyr has only a single application (so far!), but in situations where there are multiple applications then keeping those applications (and sometimes their associated infrastructure, depending on other factors) in separate Pulumi projects is generally recommended. In some ways, &amp;ldquo;use case&amp;rdquo; is an aggregation of other factors&amp;mdash;a new application with a distinct set of resources that do not share a lifecycle with existing resources and are potentially managed by a different team.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Company/department/team structure:&lt;/strong&gt; How a company/department/team is organized should certainly be taken into account when structuring your Pulumi projects. In Zephyr&amp;rsquo;s early days with Pulumi, a single team managed everything so a single project was fine. Now with multiple teams, Zephyr will likely find using multiple projects to be more effective.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security:&lt;/strong&gt; Closely related to the previous bullet, security is a factor that affects how projects and stacks should be structured. In Zephyr&amp;rsquo;s case, the ability for one team to affect another team&amp;rsquo;s resources was present when everything was a single project. With multiple projects, Zephyr can use stack permissions and role-based access control (RBAC) in Pulumi Cloud to appropriately control &lt;em&gt;who&lt;/em&gt; can affect &lt;em&gt;what&lt;/em&gt;. (The IaC Best Practices blog post on &lt;a href="https://www.pulumi.com/blog/iac-best-practices-implementing-rbac-and-security/"&gt;RBAC and security&lt;/a&gt; discusses this topic in more detail.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The relationship between resources:&lt;/strong&gt; The relationships between resources&amp;mdash;such as which resources are dependent upon other resources&amp;mdash;is another factor that feeds into your Pulumi project structure. A &amp;ldquo;shared infrastructure layer&amp;rdquo; is often best handled in a separate project of its own, but may pull in resources that might otherwise have been kept with an application in its own project. (In some respects, this is a &amp;ldquo;counter-balance&amp;rdquo; to the first bullet point.) On the other hand, resources that belong exclusively to a specific application or function might be best bundled in the same Pulumi project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resource lifecycle:&lt;/strong&gt; Do resources have a shared lifecycle, meaning they are always created, updated, or deleted together? If so, then those resources should be placed in the same project. If the resources do have a shared lifecycle, then placing them in the same project simplifies lifecycle management. A great example of resources with a shared lifecycle would be a Kubernetes Deployment and the associated Kubernetes Service.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Change rate of resources:&lt;/strong&gt; Some resources may change very infrequently. Consider the example of an &lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html"&gt;AWS VPC&lt;/a&gt; and associated objects. Once it is created, it may not change for extended periods of time, if ever. On the other hand, some resources may change frequently (in other words, these resources have a high change rate). A fast-moving software development team may revise the Kubernetes Deployment regularly to pull in a new container image with updated code. In a single project, combining resources that have a high change rate with resources that have a low change rate may be suboptimal.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blast radius:&lt;/strong&gt; Finally, there is the consideration of what happens in the event of human error, and limiting the &amp;ldquo;blast radius.&amp;rdquo; With a single project, &lt;em&gt;all&lt;/em&gt; the resources in that project are within the blast radius&amp;mdash;all of them could potentially be affected in the event of human error. Using separate Pulumi projects can limit the blast radius. It&amp;rsquo;s not something that should drive the decision all by itself, but it &lt;em&gt;is&lt;/em&gt; a factor to be considered.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that you have an idea of some of the factors that affect how users structure their Pulumi projects, what are the advantages and disadvantages of each approach?&lt;/p&gt;
&lt;p&gt;For single projects, the key advantage is &lt;em&gt;simplicity.&lt;/em&gt; There&amp;rsquo;s no denying that a single project is simpler and less complex than working with multiple projects. This advantage, though, is also the key disadvantage. A single project does not afford the flexibility to let different teams manage different aspects of the infrastructure, for example, nor does it offer the ability to split resources that don&amp;rsquo;t have a shared lifecycle. And, as mentioned earlier, blast radius is a concern.&lt;/p&gt;
&lt;p&gt;For multiple projects, the key advantage is &lt;em&gt;flexibility.&lt;/em&gt; It&amp;rsquo;s true that the added flexibility comes at the cost of some added complexity (a future post will explore one aspect of that added complexity in the form of StackReferences). However, you gain the ability to separate infrastructure resources according to team ownership/responsibility, relationship to other resources (i.e., grouping resources in a &amp;ldquo;shared infrastructure layer&amp;rdquo;), or any of the other dimensions mentioned above.&lt;/p&gt;
&lt;p&gt;In some ways, the decision points for single vs. multiple projects are analogous to the decision points for monolith vs. microservice: independent scaling and flexibility versus simplicity.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;In general, most customers end up using multiple Pulumi projects per use case.&lt;/em&gt; As you&amp;rsquo;ve seen, though, there are a number of factors that influence that decision and will affect how many projects end up in use.&lt;/p&gt;
&lt;h2 id="moving-forward-with-projects"&gt;Moving Forward with Projects&lt;/h2&gt;
&lt;p&gt;Zephyr had a number of factors in play, not the least of which was the organizational change resulting from their growth and success. In the end, Zephyr decided to restructure their use of Pulumi for the online retail store into three separate projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An infrastructure project: This project handled the AWS VPC and related infrastructure resources.&lt;/li&gt;
&lt;li&gt;A platform project: This project managed the Kubernetes cluster(s).&lt;/li&gt;
&lt;li&gt;An application project: This project handled the actual online retail store itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;You can use a combination of code refactoring and &lt;code&gt;pulumi import&lt;/code&gt; to assist in the process of restructuring Pulumi projects, but it some cases it may be easier to create new infrastructure and migrate over to it.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In the &lt;a href="https://www.pulumi.com/blog/iac-best-practices-understanding-code-organization-stacks/"&gt;code organization and stacks&lt;/a&gt; post, you read about some of the options for where to store Pulumi code relative to the applications it supports. At that time, Zephyr had decided to go with what&amp;rsquo;s called a monorepo approach, meaning that the Pulumi code was stored in the same repository as the application code. In conjunction with the project reorganization, Zephyr also decided to change how their code was organized:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Code for the infrastructure project moved to a separate &amp;ldquo;zephyr-infra&amp;rdquo; repository&lt;/li&gt;
&lt;li&gt;A new &amp;ldquo;zephyr-k8s&amp;rdquo; repository housed the code for the platform project&lt;/li&gt;
&lt;li&gt;The code for the online retail store application itself remained in the same &amp;ldquo;zephyr-app&amp;rdquo; repository&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that relocating code (to a different GitHub repository or different filesystem location within a repository) generally has no impact on Pulumi. As long as the project name and stack name do not change, then changing the filesystem location won&amp;rsquo;t affect anything&amp;mdash;keeping in mind, of course, that filesystem paths and such may need to be adjusted in code to account for any such changes.&lt;/p&gt;
&lt;p&gt;After restructuring their projects, Zephyr&amp;rsquo;s arrangement of Pulumi projects and stacks, along with their associated GitHub repositories, looks something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="structuring-projects-stacks.jpg" alt="A diagram showing three Pulumi projects and stacks along with associated GitHub repositories"&gt;&lt;/p&gt;
&lt;p&gt;These repositories are available for you to review on GitHub (&lt;a href="https://github.com/pulumi/zephyr-infra"&gt;zephyr-infra&lt;/a&gt;, &lt;a href="https://github.com/pulumi/zephyr-k8s"&gt;zephyr-k8s&lt;/a&gt;, and &lt;a href="https://github.com/pulumi/zephyr-app"&gt;zephyr-app&lt;/a&gt;); use the branch/tag selector to find the &lt;code&gt;blog/multi-project&lt;/code&gt; branch in each repository to see the state of the code as of this blog post. Be aware that the process for standing up the Zephyr online store is now a bit more complex; check the instructions in each repository for more details if you&amp;rsquo;d like to give these projects a spin.&lt;/p&gt;
&lt;h2 id="summarizing-best-practices-pulumi-project-structures"&gt;Summarizing Best Practices: Pulumi Project Structures&lt;/h2&gt;
&lt;p&gt;This post covered the following recommended practices for working with Pulumi:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use multiple Pulumi projects&lt;/strong&gt; when you need the added flexibility that multiple Pulumi projects offer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Align your Pulumi projects&lt;/strong&gt; according to external factors like company/department/team structure, resource dependencies, lifecycle, and use case/application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Change the filesystem location&lt;/strong&gt; for Pulumi code as needed without impacting the resources managed by that code, as long as the Pulumi project name and stack name don&amp;rsquo;t change.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The next post will focus on some specific technical details related to Zephyr&amp;rsquo;s project reorganization; specifically, how they used StackReferences to share information between their Pulumi projects. Stay tuned!&lt;/p&gt;</description><author>Christian Nunciato</author><author>Scott Lowe</author><category>best-practices</category><category>cloud-engineering</category></item><item><title>IaC Best Practices: Enabling Developer Stacks &amp; Git Branches</title><link>https://www.pulumi.com/blog/iac-best-practices-enabling-developer-stacks-git-branches/</link><pubDate>Fri, 10 Mar 2023 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/iac-best-practices-enabling-developer-stacks-git-branches/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/iac-best-practices-enabling-developer-stacks-git-branches/index.png" /&gt;
&lt;p&gt;In the first post about &lt;a href="https://www.pulumi.com/blog/iac-best-practices-understanding-code-organization-stacks/"&gt;code organization and stacks&lt;/a&gt;, we introduced Zephyr, a fictional company that uses Pulumi to manage its online retail store. Following on from that post, which discusses code organization and stacks, this post explores two more questions users frequently ask when working with Pulumi in teams &amp;mdash; namely, How can I best enable multiple developers to collaborate on a Pulumi project? And how can I use Git and Git branching to support this kind of collaboration? In this post, we&amp;rsquo;ll provide some guidance and best practices around these topics, using Zephyr and its online store as the use case.&lt;/p&gt;
&lt;p&gt;The ultimate goal of this series is to discuss best practices for using Pulumi to manage a fairly complex containerized application. However, it&amp;rsquo;s important to note that these practices will emerge over the course of the series &amp;mdash; not all immediately, and not all in the beginning. This is a deliberate decision to allow you to see how Zephyr&amp;rsquo;s use of Pulumi evolves as the company grows and its retail application changes to accommodate its growth.&lt;/p&gt;
&lt;p&gt;Here are links to all of the posts in the series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-understanding-code-organization-stacks/"&gt;IaC Best Practices: Understanding Code Organization and Stacks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IaC Best Practices: Enabling Developer Stacks and Git Branches&lt;/strong&gt; (this post)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-structuring-pulumi-projects/"&gt;IaC Best Practices: Structuring Pulumi Projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-applying-stack-references/"&gt;IaC Best Practices: Applying Stack References&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-implementing-rbac-and-security/"&gt;IaC Best Practices: Implementing RBAC and Security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-using-automation-api/"&gt;IaC Best Practices: Using Automation API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-summarizing-key-learnings"&gt;IaC Best Practices: Summarizing Key Learnings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="existing-iac-workflow"&gt;Existing IaC Workflow&lt;/h2&gt;
&lt;p&gt;When we last met up with the Zephyr team, they were off and running, managing their newly refactored online store, Zephyr Archaeotech Emporium, with a single &lt;a href="https://www.pulumi.com/docs/concepts/projects/"&gt;Pulumi project&lt;/a&gt; and two &lt;a href="https://www.pulumi.com/docs/concepts/stack/"&gt;Pulumi stacks&lt;/a&gt; &amp;mdash; one for development (&lt;code&gt;dev&lt;/code&gt;) and another for production (&lt;code&gt;prod&lt;/code&gt;). The team had chosen to use one Git repository (a monorepo) to manage the code for the online store and its infrastructure after refactoring the store into a set of containerized microservices deployed with Kubernetes on &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/what-is-eks.html"&gt;Amazon EKS&lt;/a&gt;.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;For a snapshot of the code as it was at the end of the first post in the series, see the &lt;a href="https://github.com/pulumi/zephyr-app/tree/blog/original"&gt;&lt;code&gt;blog/original&lt;/code&gt;&lt;/a&gt; branch of the &lt;a href="https://github.com/pulumi/zephyr-app"&gt;&lt;code&gt;pulumi/zepyhr-app&lt;/code&gt;&lt;/a&gt; repository on GitHub.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Up to now, the two-stack approach has worked well for the team. It&amp;rsquo;s allowed them to focus on building new features, integrating those features into the shared &lt;code&gt;dev&lt;/code&gt; environment by developing and testing locally (which we&amp;rsquo;ll cover in a future post) and merging their &lt;a href="https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow"&gt;feature branches&lt;/a&gt; regularly into the base branch, &lt;code&gt;main&lt;/code&gt;, using standard GitHub pull-request workflows. Around once a day, as the team&amp;rsquo;s changes are merged into &lt;code&gt;main&lt;/code&gt;, the team lead (and original author of the Pulumi program) deploys them into the &lt;code&gt;dev&lt;/code&gt; environment with Pulumi with the following commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git pull origin main
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; infra
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi stack &lt;span class="k"&gt;select&lt;/span&gt; zephyr/dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi up
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This builds new container images for any services whose code has changed since the last stack update, pushes those images to &lt;a href="https://docs.aws.amazon.com/AmazonECR/latest/userguide/what-is-ecr.html"&gt;Amazon ECR&lt;/a&gt;, and updates the EKS cluster to deploy the services automatically.&lt;/p&gt;
&lt;p&gt;Then, around once a week, as new features are ready for customers, the team lead deploys the &lt;code&gt;main&lt;/code&gt; branch of the repository into production similarly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git pull origin main
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; infra
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi stack &lt;span class="k"&gt;select&lt;/span&gt; zephyr/prod
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi up
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The deployment process is still a bit more manual than the team would like &amp;mdash; but since updates typically take only a few minutes, the team lead is generally satisfied with the status quo.&lt;/p&gt;
&lt;p&gt;A few more details worth noting out about Team Zephyr&amp;rsquo;s current setup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Both of the project&amp;rsquo;s stack-configuration files, &lt;code&gt;Pulumi.dev.yaml&lt;/code&gt; and &lt;code&gt;Pulumi.prod.yaml&lt;/code&gt;, are checked into version control. This is safe and considered good practice (even with &lt;a href="https://www.pulumi.com/docs/concepts/secrets/"&gt;secrets&lt;/a&gt;) because it allows the team to review or change the configuration of a given stack easily by viewing or editing the code in each file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;main&lt;/code&gt; branch is the only long-lived branch in the repository; all others (e.g., developers&amp;rsquo; feature branches) are deleted once they&amp;rsquo;re merged into &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Thanks to this workflow, the application deployed into the &lt;code&gt;dev&lt;/code&gt; environment tracks closely with the latest code in the &lt;code&gt;main&lt;/code&gt; branch. As such, the &lt;code&gt;dev&lt;/code&gt; stack serves as a kind of pre-prod or integration environment that not only allows the team to dogfood new features as they&amp;rsquo;re added, but also to catch bugs or other regressions before they escape into production. When things do go wrong, the team can review the list of recent stack updates to identify the source of the defect and get it fixed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Zephyr is using the Pulumi Service backend with a single organization (that everyone on the team belongs to) and separate AWS accounts for the &lt;code&gt;dev&lt;/code&gt; and &lt;code&gt;prod&lt;/code&gt; environments. Everyone on the team has read/write access to the AWS account used for development, but only the team lead has access to the AWS account used for production.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="growing-pains"&gt;Growing Pains&lt;/h2&gt;
&lt;p&gt;Life for Team Zephyr was good &amp;mdash; for a while.&lt;/p&gt;
&lt;p&gt;Then one day during a particularly active sprint, a developer who&amp;rsquo;d started working on an improvement to the checkout process realized it might benefit from an updated version of Redis, which appeared to be presently &lt;a href="https://github.com/pulumi/zephyr-app/blob/e61b5b02b0eebe14782fba8ee0343952353c9fd0/infra/index.ts#L1045"&gt;pinned at 6.0 in the Pulumi program&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkoutRedisDeployment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;k8s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Deployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;checkout-redis-deployment&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;containers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;redis:6.0-alpine&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A quick glance at the &lt;code&gt;dev&lt;/code&gt; and &lt;code&gt;prod&lt;/code&gt; stacks in the Pulumi Service confirmed this was the case.&lt;/p&gt;
&lt;p&gt;After a quick spike to validate the enhancement locally, the developer felt confident that a Redis upgrade was the way to go &amp;mdash; but since the deployed version of Redis would ultimately dictate the code that&amp;rsquo;d have to be written to finish the feature for real, they couldn&amp;rsquo;t really be sure without testing the theory with a full deployment. What they really wanted was to be able to deploy a Redis upgrade, along with the associated code changes, into an AWS environment as much like production as possible &amp;mdash; the &lt;code&gt;dev&lt;/code&gt; environment, say &amp;mdash; without having to go through the process of making a pull request, getting it merged, and waiting for the team lead to deploy the &lt;code&gt;dev&lt;/code&gt; stack only to realize later that the change didn&amp;rsquo;t actually work as expected, and be forced to revert.&lt;/p&gt;
&lt;p&gt;In situations like these, it can be tempting to suggest that the developer simply pick up and use the &lt;code&gt;dev&lt;/code&gt; stack &amp;mdash; especially on small, fast-moving teams like Zephyr&amp;rsquo;s where everyone already has access to the Pulumi stack and the shared cloud environment. Indeed, Pulumi makes this sort of thing easy: to deploy their local changes into the &lt;code&gt;dev&lt;/code&gt; environment, our developer need only check out their feature branch, select the &lt;code&gt;dev&lt;/code&gt; stack, run &lt;code&gt;pulumi up&lt;/code&gt;, and then wait a few moments for the changes to be deployed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; infra
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi stack &lt;span class="k"&gt;select&lt;/span&gt; zephyr/dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi up
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This approach definitely works &amp;mdash; but it presents at least a few problems:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The team would lose all of the practical benefits of having the &lt;code&gt;dev&lt;/code&gt; stack track closely with the latest content in &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The team lead&amp;rsquo;s deployment of &lt;code&gt;main&lt;/code&gt; into &lt;code&gt;dev&lt;/code&gt; later that day would overwrite the developer&amp;rsquo;s own feature-branch deployment. There&amp;rsquo;s a good chance the lead would notice the diff of the deployment preview and deduce that someone else on the team had possibly deployed into &lt;code&gt;dev&lt;/code&gt; out of band:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; kubernetes:apps/v1:Deployment (create):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; spec: {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; template: {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; spec: {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; containers: [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; [0]: {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- image: &amp;#34;redis:6.0-alpine&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ image: &amp;#34;redis:7.0-alpine&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But if they didn&amp;rsquo;t, the developer&amp;rsquo;s ad-hoc deployment would be lost. At best, it&amp;rsquo;d force both team members to drop what they were doing and get in touch to figure out what to do next. At worst, it could lead to confusion and the potential for shipping untested code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In addition to raising the risk of developers overwriting each other&amp;rsquo;s work, active collaboration the same stack increases the likelihood of deployment conflicts. On the one hand, this is a good thing in that Pulumi prevents same-stack updates from running concurrently:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ pulumi up
error: [409] Conflict: Another update is currently in progress.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But on the other, it&amp;rsquo;s a not-so-good thing in that it&amp;rsquo;s a reduction in development velocity: The update that can&amp;rsquo;t run because another update is in progress is an update that will have to wait until sometime later. The more often this happens, the longer it ultimately takes for the team to get the validation it needs to keep moving forward.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So facing these issues, what should the Zephyr team do? The &lt;code&gt;dev&lt;/code&gt; stack had originally been created for rapid development &amp;mdash; and that worked well, when there was only one developer working on infrastructure. With several, however, things clearly get complicated. Should the team be using a &lt;code&gt;dev&lt;/code&gt; stack at all? If so, how? And if not, what should they be doing instead?&lt;/p&gt;
&lt;h2 id="stepping-back-to-reassess"&gt;Stepping Back to Reassess&lt;/h2&gt;
&lt;p&gt;With what we&amp;rsquo;ve learned so far, we can return to the questions we raised at the beginning of this post and begin to sketch out a few recommendations for Team Zephyr. Those questions, again, were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;How can I best enable multiple developers to collaborate on a Pulumi project?&lt;/strong&gt; For Zephyr, an ideal answer to this question would give every member of the team the ability to iterate on both application and infrastructure code concurrently, deploying as often as needed without having to worry about disrupting the work or the velocity of anyone else on the team.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;How can I use Git and Git branching to support this collaboration?&lt;/strong&gt; So far, the feature-branch/pull-request workflow has been serving the Zephyr team well. Should they continue on the same path, merging frequently to &lt;code&gt;main&lt;/code&gt; and deploying to &lt;code&gt;dev&lt;/code&gt; and &lt;code&gt;prod&lt;/code&gt; from there? Or is there some alternative workflow that might serve the team better, either now or at some time in the future?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s answer these questions now by taking a look at how the Zephyr team handled them.&lt;/p&gt;
&lt;h3 id="using-developer-stacks"&gt;Using Developer Stacks&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;ve seen a few ways that having multiple developers share a single stack for ad-hoc deployment can lead to various problems. But we&amp;rsquo;ve also seen at least one situation in which being able to run ad-hoc deployments &amp;mdash; without the risk of colliding with existing environments &amp;mdash; can be quite valuable. In fact there are many reasons you might want to do this &amp;mdash; and fortunately Pulumi makes it quite easy to do so.&lt;/p&gt;
&lt;p&gt;The best practice is to allow developers to create and manage their own Pulumi stacks. In this workflow, developers working on feature branches are able to iterate freely on both application and infrastructure, deploying as often as necessary into a shared cloud environment (typically a sandbox environment, isolated from production) and destroying cloud infrastructure when it&amp;rsquo;s no longer needed.&lt;/p&gt;
&lt;p&gt;For the developer, the typical workflow looks something like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check out a new feature branch&lt;/li&gt;
&lt;li&gt;Make changes to application and/or infrastructure code&lt;/li&gt;
&lt;li&gt;Select your personal developer stack with &lt;code&gt;pulumi stack select&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Deploy your stack into your team&amp;rsquo;s shared cloud environment with &lt;code&gt;pulumi up&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Commit your code and submit a pull request, optionally including a link either to the Pulumi update (e.g., when using the Pulumi Service) or to certain key resources belonging to the stack &amp;mdash; for example, an application&amp;rsquo;s primary HTTP endpoint&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The benefits of a workflow like this one are many:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No more resource-naming collisions. Because Pulumi names cloud resources &lt;a href="https://www.pulumi.com/docs/concepts/resources/names/#autonaming"&gt;uniquely by default&lt;/a&gt;, no two stacks will ever produce cloud resources with the same names.&lt;/li&gt;
&lt;li&gt;No more deployment conflicts. When every developer has a stack of their own, they can deploy to the sandboxed environment as often as necessary, without having to wait for another deployment to finish first.&lt;/li&gt;
&lt;li&gt;Everyone on the team has their own production-like environment to use for end-to-end testing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/iac-best-practices-enabling-developer-stacks-git-branches/dev-stacks-git-branches-workflow.jpg" alt="An image showing the main branch deploying with test and prod stacks and feature branches deploying with developer stacks"&gt;&lt;/p&gt;
&lt;p&gt;For Zephyr, the quickest path to adopting this workflow was to use the &lt;code&gt;dev&lt;/code&gt; stack as a foundation for new developer stacks. To do that, team members simply ran the following Pulumi commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi stack init zephyr/developer-name --copy-config-from zephyr/dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi up
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Developer stacks are considered ephemeral, so they&amp;rsquo;re torn down with &lt;code&gt;pulumi destroy&lt;/code&gt; when not in use in order to cut down on cost. Stack configuration files (e.g., &lt;code&gt;Pulumi.developer-name.yaml&lt;/code&gt;) are checked into version control and stored alongside other stacks like &lt;code&gt;Pulumi.dev.yaml&lt;/code&gt; and &lt;code&gt;Pulumi.prod.yaml&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="keeping-feature-branch-workflows"&gt;Keeping Feature-Branch Workflows&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;ve also seen that the Zephyr team&amp;rsquo;s existing Git workflow (short-lived feature branches merged frequently into a long-lived base branch) has worked well for them &amp;mdash; and it turns out this workflow also works well when combined with the use of developer stacks.&lt;/p&gt;
&lt;p&gt;For example, after creating and deploying a new stack to validate and test the proposed Redis upgrade, our Zephyr developer simply included a link to the application (deployed with their dev stack) in the associated GitHub pull request. Reviewers were then able to follow the link to the online store to see the effects of the shopping-cart change firsthand, which meant they were able to approve and get the change merged quickly and confidently knowing it&amp;rsquo;d work when deployed into production.&lt;/p&gt;
&lt;h3 id="naming-stacks-clearly"&gt;Naming Stacks Clearly&lt;/h3&gt;
&lt;p&gt;Lastly, having adopted the use of developer stacks, the Zephyr team realized the name &amp;ldquo;dev&amp;rdquo; probably didn&amp;rsquo;t work quite as well as it once had for the &lt;code&gt;dev&lt;/code&gt; stack. Since the stack and its long-running deployment had essentially been serving (and continued to serve) as an environment for testing changes before releasing them into production, the team decided to rename the &lt;code&gt;dev&lt;/code&gt; stack &lt;code&gt;test&lt;/code&gt; instead. To do that, they simply ran:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi stack &lt;span class="k"&gt;select&lt;/span&gt; zephyr/dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi stack rename zephyr/test
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="viewing-the-next-iteration-of-code"&gt;Viewing the Next Iteration of Code&lt;/h2&gt;
&lt;p&gt;You can view the second iteration of Zephyr&amp;rsquo;s Pulumi and application code &amp;mdash; the iteration that corresponds to the decisions described in this blog post &amp;mdash; by navigating to the &lt;a href="https://github.com/pulumi/zephyr-app/"&gt;Zephyr App GitHub repository&lt;/a&gt;. From the branch/tag dropdown, switch from the &lt;code&gt;main&lt;/code&gt; branch to the &lt;a href="https://github.com/pulumi/zephyr-app/tree/blog/dev-stacks"&gt;&lt;code&gt;blog/dev-stacks&lt;/code&gt;&lt;/a&gt; branch.&lt;/p&gt;
&lt;p&gt;From that GitHub repository, you can also choose to deploy the Pulumi code yourself. Full instructions for deploying the code are found in the repository.&lt;/p&gt;
&lt;h2 id="summarizing-best-practices-developer-stacks-and-git-branches"&gt;Summarizing Best Practices: Developer Stacks and Git Branches&lt;/h2&gt;
&lt;p&gt;In this post, we covered several new guidelines for working collaboratively with Pulumi, all aimed at optimizing for high team and developer velocity:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use short-lived feature branches&lt;/strong&gt; and merge them frequently into a single base branch.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploy the base branch regularly into a long-running pre-prod environment&lt;/strong&gt; to continuously test the integration of the components of the system.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enable developers to create and use dev stacks of their own&lt;/strong&gt; as sandboxes for development, testing, and collaboration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Name your stacks clearly&lt;/strong&gt; and in alignment with the environments they deploy into &amp;mdash; &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;staging&lt;/code&gt;, &lt;code&gt;production&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The next Zephyr blog post will dive into how the Zephyr team structures their Pulumi projects. Stay tuned!&lt;/p&gt;</description><author>Christian Nunciato</author><author>Scott Lowe</author><category>best-practices</category><category>cloud-engineering</category><category>aws</category><category>eks</category></item><item><title>IaC Best Practices: Understanding Code Organization &amp; Stacks</title><link>https://www.pulumi.com/blog/iac-best-practices-understanding-code-organization-stacks/</link><pubDate>Mon, 20 Feb 2023 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/iac-best-practices-understanding-code-organization-stacks/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/iac-best-practices-understanding-code-organization-stacks/index.png" /&gt;
&lt;p&gt;This is the first in a series of blog posts that explores how a fictional company&amp;mdash;Zephyr Archaeotech Emporium&amp;mdash;uses Pulumi to manage their online retail store. This post explores a couple of common questions that users ask when working with Pulumi; specifically, where should I store my Pulumi code? And how do I support multiple environments with Pulumi? This post will provide some guidance and &lt;a href="https://www.pulumi.com/what-is/what-is-infrastructure-as-code/"&gt;Infrastructure as Code&lt;/a&gt; best practices around these topics, using Zephyr and their online store as the use case.&lt;/p&gt;
&lt;p&gt;The ultimate goal of this series is to discuss recommended practices for using Pulumi to manage a fairly complex containerized application. However, it&amp;rsquo;s important to note that these practices will emerge over the course of the series&amp;mdash;not all immediately, and not all in the beginning. This is a deliberate decision to allow you to see how Zephyr&amp;rsquo;s use of Pulumi evolves as the company grows and their online retail store application changes to accommodate their growth.&lt;/p&gt;
&lt;p&gt;Here are links to all the blog posts in the series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IaC Best Practices: Understanding Code Organization and Stacks&lt;/strong&gt; (this post)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-enabling-developer-stacks-git-branches/"&gt;IaC Best Practices: Enabling Developer Stacks and Git Branches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-structuring-pulumi-projects/"&gt;IaC Best Practices: Structuring Pulumi Projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-applying-stack-references/"&gt;IaC Best Practices: Applying Stack References&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-implementing-rbac-and-security/"&gt;IaC Best Practices: Implementing RBAC and Security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-using-automation-api/"&gt;IaC Best Practices: Using Automation API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/iac-best-practices-summarizing-key-learnings"&gt;IaC Best Practices: Summarizing Key Learnings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="setting-up-the-scenario"&gt;Setting Up the Scenario&lt;/h2&gt;
&lt;p&gt;Zephyr is short for Zephyr Archaeotech Emporium, the fictional company in our scenario. Zephyr is an online retailer that specializes in the sale of &amp;ldquo;rare arcane artifacts and replicas.&amp;rdquo; Over the past few years, the company has experienced a significant increase in its online presence, making it easier for customers to purchase unique and mysterious items. Zephyr&amp;rsquo;s collection includes a variety of rare and unusual objects that are difficult to find elsewhere, making it a popular destination for collectors, enthusiasts, and adventurers.&lt;/p&gt;
&lt;h2 id="moving-to-a-containerized-architecture"&gt;Moving to a Containerized Architecture&lt;/h2&gt;
&lt;p&gt;Zephyr&amp;rsquo;s application is the second generation of their online store. Their first generation was a monolith that they manually deployed. It was sufficient in Zephyr&amp;rsquo;s early days, but as word about Zephyr spread and the volume of traffic to their online store increased, Zephyr found it increasingly difficult to deploy small, incremental changes to the store&amp;rsquo;s code. As a result, the velocity of their development efforts slowed. In preparation for the next phase of their growth&amp;mdash;driven by robust sales of arcane artifacts&amp;mdash;Zephyr&amp;rsquo;s team evaluated a number of different architectures that would enable them to increase development velocity and give them greater flexibility in scaling different aspects of their online store. In the end, they settled on a containerized architecture deployed to &lt;a href="https://www.pulumi.com/kubernetes/"&gt;Kubernetes&lt;/a&gt; because some of the existing team was already familiar with these technologies.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a diagram of their second-generation application architecture:&lt;/p&gt;
&lt;p&gt;&lt;img src="app-architecture-vpc-eks-cluster.jpg" alt="A diagram depicting an application consisting of containerized microservices"&gt;&lt;/p&gt;
&lt;p&gt;As you can see in the diagram, the application has been broken down into microservices. The UI service communicates with other backend services via HTTP APIs; the various backend services communicate with various databases like MySQL and Redis for data persistence and/or caching. The backend services are responsible for implementing the functionality of a typical e-commerce site like orders, checking out, and managing the inventory catalog. While the choice of Kubernetes does afford Zephyr some much-needed functionality&amp;mdash;like the ability to easily scale different services within the application&amp;mdash;it also brings with it an added level of complexity that did not exist with their previous monolithic architecture. The Kubernetes manifest to deploy their application tops out at just over 1,000 lines of YAML!&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;Astute readers may note that Zephyr&amp;rsquo;s application looks very similar to &lt;a href="https://github.com/aws-containers/retail-store-sample-app/"&gt;this application&lt;/a&gt;; in truth, this series uses a fork of the AWS Containers retail store example as the application in this fictional scenario. (Many thanks to the AWS Containers DA team for their hard work here!) This is an admittedly over-engineered application, but it does afford the opportunity to address a range of customer scenarios in this blog series.&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="managing-infrastructure-and-application-deployments"&gt;Managing Infrastructure and Application Deployments&lt;/h2&gt;
&lt;p&gt;As part of the switch to their new architecture, Zephyr decided they wanted to use Pulumi to manage both the infrastructure and the application deployment. Why Pulumi? Zephyr’s team recognized that moving to microservices on Kubernetes was going to make it more difficult to create reproducible, consistent implementations of their application. Zephyr&amp;rsquo;s team also knew that adopting infrastructure as code would help them with fast and repeatable deployments with little additional complexity. Another bonus was being able to use programming languages they already knew.&lt;/p&gt;
&lt;p&gt;As they prepared to embark on using Pulumi, a couple of questions came up for the Zephyr team:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Where should they store their Pulumi code?&lt;/strong&gt; The ability for Zephyr&amp;rsquo;s developers to use a full programming language to manage the infrastructure and deployment of their online store is a huge plus, but should this code go in the same repository as their application code? Or a different repository?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How do they address the need for multiple environments?&lt;/strong&gt; It&amp;rsquo;s clear to the Zephyr development team that they&amp;rsquo;ll need multiple instances of the online store (for things like testing, QA, or development). What&amp;rsquo;s the best way to handle this when using Pulumi?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s examine each of these issues in a bit more detail, and see how Zephyr chose to proceed.&lt;/p&gt;
&lt;h3 id="storing-pulumi-code"&gt;Storing Pulumi Code&lt;/h3&gt;
&lt;p&gt;When it comes to answering the question of where to store Pulumi code relative to the application(s) it supports, there are two basic options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;In the same repository as your application code:&lt;/strong&gt; This approach has the advantage of being simpler, but may not offer the necessary flexibility when dealing with multiple teams or varying levels of access control.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In a separate repository from your application code:&lt;/strong&gt; Using a separate repository adds some complexity, but does provide greater flexibility to more easily address a variety of organizational requirements and varying levels of access control.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So which approach is best? That will depend on a number of different factors, many of which are outlined in the &lt;a href="https://www.pulumi.com/docs/using-pulumi/organizing-projects-stacks/"&gt;organizing projects &amp;amp; stacks documentation&lt;/a&gt;. Some other factors that users need to take into consideration include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Who is responsible for maintaining the code? If the application code and the Pulumi code are managed by different teams, then using separate repositories may be the best approach.&lt;/li&gt;
&lt;li&gt;What sort of access controls are needed? If the organization needs different access controls for the application code and the Pulumi code, then separate repositories are generally needed.&lt;/li&gt;
&lt;li&gt;What is the relationship between the infrastructure resources managed by Pulumi and the applications? If the infrastructure is &amp;ldquo;shared,&amp;rdquo; meaning it is used by multiple applications, then keeping the Pulumi code in a separate repository may be preferred. If the infrastructure is dedicated to a specific application, then keeping the application code and the Pulumi code together may be preferred.&lt;/li&gt;
&lt;li&gt;What about CI/CD? If an organization will use a &lt;a href="https://www.pulumi.com/what-is/what-is-ci-cd/"&gt;CI/CD pipeline&lt;/a&gt;, that may affect how you organize your code. (This is something we&amp;rsquo;ll delve into more deeply later in this series.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;rsquo;s worth stressing that the answers to these questions may change over time as organizations grow and their applications evolve. Addressing that change and its effects on an organization&amp;rsquo;s Pulumi projects and stacks is something we&amp;rsquo;ll be discussing later in this series.&lt;/p&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;The discussion above is working from the assumption that Pulumi users will store their Pulumi code in a version control system, like Git. Strictly speaking, using a version control system isn&amp;rsquo;t required for Pulumi to function (there is one exception we&amp;rsquo;ll touch on later in this series when we discuss &lt;a href="https://www.pulumi.com/docs/pulumi-cloud/deployments/"&gt;Pulumi Deployments&lt;/a&gt;), but we &lt;strong&gt;very strongly&lt;/strong&gt; recommend that all users use Pulumi in conjunction with version control.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In the case of Zephyr, their team felt like a monorepo approach (storing Pulumi code and application code together) was most appropriate for their specific requirements. Zephyr is a relatively small organization with a single team of developers who are responsible for managing both the cloud infrastructure as well as the development and deployment of the online store application. Zephyr&amp;rsquo;s team also decided to use a single Pulumi project&amp;mdash;for now. (Be sure to follow this blog series to see how that evolves, and why!)&lt;/p&gt;
&lt;h3 id="addressing-the-need-for-multiple-environments"&gt;Addressing the Need for Multiple Environments&lt;/h3&gt;
&lt;p&gt;The second question Zephyr encountered is how to handle the need for multiple instances of their application. In addition to a production instance&amp;mdash;which is the instance behind Zephyr&amp;rsquo;s public-facing online store&amp;mdash;Zephyr also felt they needed an environment for the developers to use in testing changes to the online store.&lt;/p&gt;
&lt;p&gt;This use case&amp;mdash;needing to have multiple, separate instances of the infrastructure and applications created by a single Pulumi program&amp;mdash;is exactly what &lt;a href="https://www.pulumi.com/docs/concepts/stack/"&gt;Pulumi stacks&lt;/a&gt; were designed to address. Each stack is a separate instance of the resources created by a Pulumi program within a project. Further, each stack has its own independent state, and each stack has its own configuration values. Stacks can be short-lived (meaning the associated resources are also short-lived), or stacks can be long-lived. Aside from the cloud resources created by the Pulumi program, stacks are lightweight and simple to create or delete.&lt;/p&gt;
&lt;p&gt;Zephyr decided to initially start with two stacks: a production stack (named &amp;ldquo;prod&amp;rdquo;) and a development stack (named &amp;ldquo;dev&amp;rdquo;). As you&amp;rsquo;ll observe throughout this series, this is a decision that is easily adjusted over time as your organization&amp;rsquo;s requirements change.&lt;/p&gt;
&lt;h2 id="viewing-the-first-iteration-of-code"&gt;Viewing the First Iteration of Code&lt;/h2&gt;
&lt;p&gt;You can view the first iteration of Zephyr&amp;rsquo;s Pulumi and application code&amp;mdash;the iteration that corresponds to the decisions described in this blog post&amp;mdash;by navigating to &lt;a href="https://github.com/pulumi/zephyr-app/"&gt;the Zephyr App GitHub repository&lt;/a&gt;. From the branch/tag dropdown, switch from the &lt;code&gt;main&lt;/code&gt; branch to viewing the &lt;a href="https://github.com/pulumi/zephyr-app/tree/blog/original/"&gt;&lt;code&gt;blog/original&lt;/code&gt;&lt;/a&gt; branch.&lt;/p&gt;
&lt;p&gt;From that GitHub repository, you can also choose to deploy the Pulumi code yourself. Full instructions for deploying the code are found in the repository.&lt;/p&gt;
&lt;h2 id="summarizing-best-practices-organizing-code-and-stacks"&gt;Summarizing Best Practices: Organizing Code and Stacks&lt;/h2&gt;
&lt;p&gt;This article introduced you to Zephyr, the fictional company for this blog series, and reviewed the application architecture for Zephyr&amp;rsquo;s online store (a containerized application running on Kubernetes). Throughout the blog series, this application serves as the use case around which various recommended practices are discussed.&lt;/p&gt;
&lt;p&gt;In this post the following guidelines were covered:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Use version control for your Pulumi code.&lt;/strong&gt; Although it isn&amp;rsquo;t strictly required in many cases, a number of the benefits of adopting infrastructure as code are lost without the use of version control.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Place your Pulumi code into version control repositories in a way that aligns with organizational requirements.&lt;/strong&gt; There is no &amp;ldquo;right&amp;rdquo; or &amp;ldquo;wrong&amp;rdquo; approach; choose the approach that enables you to satisfy your organization&amp;rsquo;s requirements around access control, code/repo ownership, and other organizational requirements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Leverage Pulumi stacks to create multiple instances of cloud resources with a single Pulumi program.&lt;/strong&gt; Pulumi stacks were created to address the need for organizations to create multiple instances of cloud resources, each with its own state and its own set of configuration data, from a single Pulumi program.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For now, Zephyr has decided to go with a monorepo approach&amp;mdash;a single repository that contains both their application code and the Pulumi code to manage the infrastructure resources. All of the resources are defined in a single Pulumi project, with multiple stacks that correspond to development and production environments. Over the course of this series, we&amp;rsquo;ll see how Zephyr&amp;rsquo;s use of Pulumi changes as Zephyr grows and their application evolves.&lt;/p&gt;
&lt;p&gt;The next Zephyr blog post will examine how the Zephyr team adjusts their use of stacks and Git branches to support developer productivity. Stay tuned!&lt;/p&gt;</description><author>Aaron Kao</author><author>Christian Nunciato</author><author>Scott Lowe</author><category>best-practices</category><category>cloud-engineering</category><category>aws</category><category>eks</category></item></channel></rss>