<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Doppler</title>
    <description>The latest articles on Forem by Doppler (@doppler).</description>
    <link>https://forem.com/doppler</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F4535%2F327218fb-e34f-4931-bb13-750b11a5f560.png</url>
      <title>Forem: Doppler</title>
      <link>https://forem.com/doppler</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/doppler"/>
    <language>en</language>
    <item>
      <title>How we built our Secrets Rotation Engine</title>
      <dc:creator>Nic Manoogian</dc:creator>
      <pubDate>Mon, 12 Dec 2022 15:24:41 +0000</pubDate>
      <link>https://forem.com/doppler/how-we-built-our-secrets-rotation-engine-4768</link>
      <guid>https://forem.com/doppler/how-we-built-our-secrets-rotation-engine-4768</guid>
      <description>&lt;p&gt;Teams understand the value of rotating secrets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secrets are used in applications which often send logs and other metadata to third-party services, making accidental leaks a significant risk&lt;/li&gt;
&lt;li&gt;Some employees need access to certain application secrets but should only have access while it’s needed&lt;/li&gt;
&lt;li&gt;Secrets are often shared between several applications (perhaps even across multiple teams), making the exposure surface area massive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Great, rotation is a best practice. But when is the last time your team rotated a third-party service token or even your database credentials? If it was recently, how long did that process take?&lt;/p&gt;

&lt;p&gt;Performing manual rotation is risky and cumbersome. Individuals with access to production running manual workflows for every secret, every N days? Not going to happen. Like any good “thing that has to be done over and over” — this process is ripe for automation, provided it can be done safely.&lt;/p&gt;

&lt;p&gt;Doppler is in a unique position to build rotation automation because teams already:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use Doppler as their source-of-truth for secrets&lt;/li&gt;
&lt;li&gt;Configure their applications to load secrets from Doppler&lt;/li&gt;
&lt;li&gt;Rely on Doppler’s access control, observability, and auditing features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, a secret rotation feature needs to check some very important boxes before it can trusted for production use.&lt;/p&gt;

&lt;h1&gt;
  
  
  Requirements for Doppler’s Rotation Engine
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Encryption and Storage
&lt;/h2&gt;

&lt;p&gt;All rotation state data, including credentials and sensitive parameters, must be encrypted to the same standards as any other secret in our system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Atomic Operations
&lt;/h2&gt;

&lt;p&gt;Rotation must occur in “one shot”. Keeping all rotation operations atomic ensures that applications that fetch secrets from Doppler will always receive a valid (i.e. working) credential — even if Doppler’s infrastructure is interrupted (e.g. power or network failure).&lt;/p&gt;

&lt;h2&gt;
  
  
  Graceful Error Handling
&lt;/h2&gt;

&lt;p&gt;Doppler will facilitate rotation via third-party integrations and integration points which are infamous for being the most brittle parts of any software system. Some errors that the engine encounters can be retried, while others will require user intervention.&lt;/p&gt;

&lt;p&gt;Above all, data loss must not be possible. If an error or interruption occurs during any point in the rotation process, Doppler’s rotation engine must be able to recover without an issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Public Access Required
&lt;/h2&gt;

&lt;p&gt;If a secret source (e.g. a database) isn’t publicly accessibly, it won’t need to be made public for Doppler to rotate its secrets. Automatic rotation is meant to improve your security posture, not trade one set of risks for another.&lt;/p&gt;

&lt;p&gt;We used an interesting strategy to meet this requirement and the solution deserves its own post. Keep an eye out for our next rotation post on &lt;em&gt;Proxied Rotation&lt;/em&gt; to learn more.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Two-Secret Strategy
&lt;/h1&gt;

&lt;p&gt;To make automatic rotation safe and atomic, we can’t just go revoking old API keys and generating new ones. We need to generate a new key, transition applications to use it, and revoke the old key when it is no longer in use.&lt;/p&gt;

&lt;p&gt;Doppler accomplishes this by always maintaining two valid credentials: the ***&lt;strong&gt;&lt;em&gt;active&lt;/em&gt;&lt;/strong&gt;*** and the **&lt;strong&gt;&lt;em&gt;inactive&lt;/em&gt;&lt;/strong&gt;*&lt;em&gt;. Every N days (we call this the *rotation interval&lt;/em&gt;), the active credential becomes the inactive credential and the inactive credential is updated or replaced with a new active credential.&lt;/p&gt;

&lt;p&gt;Let’s take database rotation on a 15-day rotation interval as an example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;During setup (day 0), the user provides Doppler with two valid starting credentials (username/password pairs) which will be used for rotation. Let’s say they’re called &lt;code&gt;appuser1&lt;/code&gt; and &lt;code&gt;appuser2&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;At this point, when an application fetches secrets from Doppler, it’ll get back &lt;code&gt;appuser1&lt;/code&gt; with the original password&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;On day 15, Doppler will update the password  (more on how in our Proxied Rotation post) for &lt;code&gt;appuser2&lt;/code&gt; and make it the active credential

&lt;ul&gt;
&lt;li&gt;When an application fetches secrets, it’ll receive &lt;code&gt;appuser2&lt;/code&gt; and its new password&lt;/li&gt;
&lt;li&gt;The credentials for &lt;code&gt;appuser1&lt;/code&gt; are still valid but the credential is inactive and cannot be requested from Doppler&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;On day 30, Doppler will update the password for &lt;code&gt;appuser1&lt;/code&gt; and make it the active credential

&lt;ul&gt;
&lt;li&gt;When an application fetches secrets, it’ll receive &lt;code&gt;appuser1&lt;/code&gt; and its new password&lt;/li&gt;
&lt;li&gt;The credentials for &lt;code&gt;appuser2&lt;/code&gt; are still valid but the credential is inactive and cannot be requested from Doppler&lt;/li&gt;
&lt;/ul&gt;


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

&lt;p&gt;With this strategy, each credential is valid for two rotation intervals (&lt;code&gt;2*N&lt;/code&gt; days). As long as applications fetch secrets from Doppler with at least this frequency, they’re guaranteed to always be holding a valid credential.&lt;/p&gt;

&lt;h1&gt;
  
  
  Types of Rotation
&lt;/h1&gt;

&lt;p&gt;Doppler supports two types of rotation: &lt;strong&gt;Updater&lt;/strong&gt; and &lt;strong&gt;Issuer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The database rotation example above is updater rotation. The user provides Doppler with two starting credentials and Doppler updates them “in-place”.&lt;/p&gt;

&lt;p&gt;Not all credentials can be updated in place; some need to be freshly created and revoked. With issuer rotation, the user provides parameters for creating new credentials and Doppler will revoke and reissue credentials to perform the rotation.&lt;/p&gt;

&lt;p&gt;Let’s take &lt;a href="https://docs.doppler.com/docs/sendgrid"&gt;SendGrid&lt;/a&gt; API key rotation with a 15-day rotation interval as an example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;During setup (day 0), the user provides Doppler with the SendGrid API scopes that the API key needs

&lt;ul&gt;
&lt;li&gt;Doppler immediately creates a new API key with the requested scopes and this key is available to applications. Let’s call it &lt;code&gt;key1&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;On day 15, Doppler creates a new SendGrid API key, again with the requested SendGrid scopes. Let’s call it &lt;code&gt;key2&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;Applications fetching secrets from Doppler now get &lt;code&gt;key2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;key1&lt;/code&gt; is still valid but becomes inactive and cannot be requested from Doppler&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;On day 30, Doppler revokes the inactive credential (&lt;code&gt;key1&lt;/code&gt;) and issues a new one (&lt;code&gt;key3&lt;/code&gt;) to be the active credential

&lt;ul&gt;
&lt;li&gt;Applications now get &lt;code&gt;key3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;key2&lt;/code&gt; is still valid but becomes inactive and cannot be requested from Doppler&lt;/li&gt;
&lt;/ul&gt;


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

&lt;p&gt;Like the update strategy, applications are guaranteed to always fetch a valid credential as long as they re-fetch at least once every two rotation intervals. The difference with issuer rotation is that we’re continuously revoking old credentials and issuing new ones.&lt;/p&gt;

&lt;h1&gt;
  
  
  Rotation State
&lt;/h1&gt;

&lt;p&gt;To meet the atomic rotation and encryption requirements, Doppler stores rotation state as a tokenized JSON string.&lt;/p&gt;

&lt;p&gt;For those unfamiliar with tokenization: All sensitive data in Doppler is stored and managed by an isolated tokenization service in our infrastructure. Our web applications exchange sensitive data for tokens and vice versa. There’s more information on tokenization in our &lt;a href="https://docs.doppler.com/docs/security-fact-sheet"&gt;security facts sheet.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s an example JSON state object for a SendGrid rotated secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parameters&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scopes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mail.batch.create&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mail.send&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;activeIndex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;credentials&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NAME&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Doppler-sendgrid-dev-SENDGRID-1668029618931&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NuINAi1TThiDDuXQUD1pfw&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SECRET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SG.NuINAi1TThiDDuXQUD1pfw.tptg7Dt2CtGBAG2OFgI-17vBvhJOWn22mchOGtGChD8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NAME&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Doppler-sendgrid-dev-SENDGRID-1668029625563&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4rY4TZbPSX-EBoAkZrOkJA&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SECRET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pendingCredential&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;parameters&lt;/code&gt; object contains static information about how to connect with the secret source and/or how to create credentials (in the case of issuer rotation).&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;activeIndex&lt;/code&gt; field contains the index of the active credential in the &lt;code&gt;credentials&lt;/code&gt; list.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;credentials&lt;/code&gt; field contains the list of valid credentials. Depending on the type of secret, the credential may contain several fields, some sensitive and some informational.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;pendingCredential&lt;/code&gt; field contains transient state that is used during the rotation. We’ll discuss this more in the next section.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We found this structure to be sufficiently generic to accommodate all types of rotated secrets, while still allowing our core logic to perform rotation operations abstractly.&lt;/p&gt;

&lt;p&gt;When a rotation is performed, the rotation engine applies the necessary changes, builds new state, and commits the tokenized JSON object to the database.&lt;/p&gt;

&lt;h1&gt;
  
  
  Two-Phase Commits
&lt;/h1&gt;

&lt;p&gt;The rotation state must always be accurate to meet our atomicity requirements, but interruptions (e.g. power or network failure) make this tricky for updater rotations.&lt;/p&gt;

&lt;p&gt;To update a database password, the rotation engine needs to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate a new password in memory&lt;/li&gt;
&lt;li&gt;Update the DB user’s password in the source database&lt;/li&gt;
&lt;li&gt;Commit the new rotation state to the Doppler database&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This leaves a critical period between steps 2 and 3 where our engine could experience an interruption (e.g. power or network failure). When rotation is attempted after the interruption, our state might be out-of-sync with the true state of the database. Big yikes.&lt;/p&gt;

&lt;p&gt;To avoid this, we generate a new password and immediately commit the credential to the Doppler database in the &lt;code&gt;pendingCredential&lt;/code&gt; field. This occurs before we attempt to update the DB user’s password in the source database. If the engine encounters an interruption, it can check the &lt;code&gt;pendingCredential&lt;/code&gt; field during the next attempt. If a pending credential is present, the engine can test the pending username and password to verify whether or not the credential was truly updated.&lt;/p&gt;

&lt;p&gt;Issuer rotation isn’t affected by this problem, but the engine is careful to ensure that the old credential is revoked (or missing, to be resilient to interruptions) before issuing the next credential. This ensures that used credentials are not “abandoned” in error scenarios.&lt;/p&gt;

&lt;h1&gt;
  
  
  Delivering Secret Fields to the Application
&lt;/h1&gt;

&lt;p&gt;Whenever rotated secret state is created or updated, the rotation engine saves the resulting secrets as “injected secrets” into the config. This allows advanced functionality like config logs, sync integrations, webhooks, secret references, and access logs to work for rotated secrets the same way they do for static secrets.&lt;/p&gt;

&lt;p&gt;For example, if our rotated database secret was named &lt;code&gt;DB_USER&lt;/code&gt;, then the secrets &lt;code&gt;DB_USER_NAME&lt;/code&gt;, &lt;code&gt;DB_USER_PASSWORD&lt;/code&gt;, and &lt;code&gt;DB_USER_SECRET&lt;/code&gt; would all be available in the config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatic Redeployment
&lt;/h2&gt;

&lt;p&gt;Integrating Doppler into your deployment process is the most reliable way to ensure that your applications are always consuming valid secrets. When a secret is rotated, the new injected secret values are &lt;a href="https://docs.doppler.com/docs/integrations"&gt;immediately synced to any integrations&lt;/a&gt; you’ve configured, just like any other secret change in Doppler. Automatic restarts can be implemented with the &lt;a href="https://docs.doppler.com/docs/kubernetes-operator"&gt;Doppler Secrets Operator&lt;/a&gt;, &lt;a href="https://docs.doppler.com/docs/external-secrets-provider"&gt;External Secrets Operator&lt;/a&gt;, or a &lt;a href="https://docs.doppler.com/docs/webhooks"&gt;webhook automation&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Handling Errors
&lt;/h1&gt;

&lt;p&gt;In order for automatic rotation to be useful, error states need to be friendly and the dashboard needs to provide clear directions to users on how to resolve problems.&lt;/p&gt;

&lt;p&gt;Doppler distinguishes between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authentication Errors&lt;/strong&gt;: These disable the whole integration between Doppler and the service, which effectively disables all rotated secrets that use the integration to authenticate. Users are prompted to correct their authentication to continue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access Errors:&lt;/strong&gt; These disable just the rotated secret. Users are prompted to grant appropriate access back to the credential that Doppler is using to facilitate rotation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transient Errors&lt;/strong&gt;: These should be retried at some point in the future.

&lt;ul&gt;
&lt;li&gt;Doppler will automatically retry these errors with an exponential backoff, eventually disabling the rotated secret and notifying the user.&lt;/li&gt;
&lt;li&gt;When a rotated secret is in an error backoff state, this information is available in the dashboard. This includes the time of the last attempt with the error message and the time of the next attempt.&lt;/li&gt;
&lt;/ul&gt;


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

&lt;p&gt;Whenever an error requires user intervention, we notify users with the appropriate permissions to correct the issue via email.&lt;/p&gt;

&lt;h1&gt;
  
  
  Closing Thoughts
&lt;/h1&gt;

&lt;p&gt;Manual rotation is complex, time consuming, and error prone. We believe that automation is the answer — but only if the tooling is secure, reliable, and fails gracefully when necessary. After all, a tool is only as good as its failure modes.&lt;/p&gt;

&lt;p&gt;Why not &lt;a href="https://docs.doppler.com/docs/secrets-rotation"&gt;kick the tires&lt;/a&gt; and see for yourself?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Doppler Encrypted Secrets Snapshots for High Availability</title>
      <dc:creator>Ryan Blunden</dc:creator>
      <pubDate>Wed, 23 Nov 2022 05:36:42 +0000</pubDate>
      <link>https://forem.com/doppler/doppler-encrypted-secrets-snapshots-for-high-availability-1ml0</link>
      <guid>https://forem.com/doppler/doppler-encrypted-secrets-snapshots-for-high-availability-1ml0</guid>
      <description>&lt;p&gt;Ideally, fetching secrets at runtime by your application is avoided altogether using &lt;a href="https://docs.doppler.com/docs/integrations"&gt;Doppler integrations&lt;/a&gt; (e.g. Doppler Kubernetes Secrets Operator), which continuously sync secrets to your hosting environment ahead of time, but this won't always be possible.&lt;/p&gt;

&lt;p&gt;For legacy applications, on-prem, and virtualized environments where secrets must be fetched at runtime, bundling &lt;a href="https://docs.doppler.com/docs/install-cli"&gt;Doppler CLI&lt;/a&gt; secrets snapshots into your application provides a robust failsafe in the event Doppler's API is unreachable.&lt;/p&gt;

&lt;p&gt;The result is similar to a GitOps secrets approach, but without the messy business of committing encrypted secrets to Git repositories can be avoided.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build. Encrypt. Deploy. Fallback.
&lt;/h2&gt;

&lt;p&gt;Doppler secrets snapshots are bundled into the application build during CI/CD with a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler secrets download secrets.json.enc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then to fallback to a secrets snapshot when running your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler run &lt;span class="nt"&gt;--fallback&lt;/span&gt; secrets.json.enc &lt;span class="nt"&gt;--&lt;/span&gt; ./app-start.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secrets snapshots aren't just for high availability. They're perfect for network-restricted environments and offer protection if Doppler's API rate limit is exceeded.&lt;/p&gt;

&lt;p&gt;The addition of the &lt;code&gt;--fallback-only&lt;/code&gt; instructs the CLI only to use the snapshot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler run &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--fallback&lt;/span&gt; secrets.json.enc &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--fallback-only&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; ./app-start.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's look into passphrase usage for encrypting and decrypting secrets snapshots.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passphrase
&lt;/h2&gt;

&lt;p&gt;By default, the CLI configuration is used to construct the passphrase, which is typically the Service Token value exposed via the DOPPLER_TOKEN environment variable. While this is the simplest solution, setting the passphrase explicitly has the benefits of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only using the &lt;code&gt;DOPPLER_TOKEN&lt;/code&gt; for authentication&lt;/li&gt;
&lt;li&gt;Allowing the passphrase and DOPPLER_TOKEN values to be rotated independently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly, your implementation must guarantee the passphrase remains constant from build to deployment during a release. As these are infrastructure secrets, we recommend a dedicated infra project for each application containing only the &lt;code&gt;DOPPLER_TOKEN&lt;/code&gt; and (should you use a passphrase) a &lt;code&gt;DOPPLER_PASSPHRASE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bpzJAp5U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/znck0v4xl1q4dqhkqzl2.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bpzJAp5U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/znck0v4xl1q4dqhkqzl2.jpeg" alt="" width="880" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;‎&lt;a href="https://docs.doppler.com/docs/integrations"&gt;Doppler integrations&lt;/a&gt; then keep infrastructure secrets in sync between your build and deployment environments, eliminating the risks associated with manual copying and pasting.&lt;/p&gt;

&lt;p&gt;The following video demonstrates how to set up an infra project in three simple steps:&lt;/p&gt;

&lt;p&gt;%[&lt;a href="https://vimeo.com/769739281"&gt;https://vimeo.com/769739281&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;With the required pieces in place, let's move on to creating and using secrets snapshots.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build
&lt;/h2&gt;

&lt;p&gt;With the &lt;code&gt;DOPPLER_TOKEN&lt;/code&gt; and &lt;code&gt;DOPPLER_PASSPHRASE&lt;/code&gt; injected into your build environment (e.g. GitHub Action Secrets), we can now create the secrets snapshot file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler secrets download &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--passphrase&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOPPLER_PASSPHRASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  secrets.json.enc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secrets snapshots also support name transformers and download formats:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ASP.NET Core secrets snapshot&lt;/span&gt;
doppler secrets download &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--passphrase&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOPPLER_PASSPHRASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--format&lt;/span&gt; dotnet-json &lt;span class="se"&gt;\&lt;/span&gt;
 appsettings.json.enc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure the secrets snapshot file is included in your build, then you're done!&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy
&lt;/h2&gt;

&lt;p&gt;With the &lt;code&gt;DOPPLER_TOKEN&lt;/code&gt; and &lt;code&gt;DOPPLER_PASSPHRASE&lt;/code&gt; injected into the deployment environment via automation (e.g Ansible), the Doppler CLI will attempt to fetch the latest version of secrets using the secrets snapshot as a fallback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--passphrase&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOPPLER_PASSPHRASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--fallback&lt;/span&gt; secrets.json.enc &lt;span class="nt"&gt;--&lt;/span&gt; ./start-app.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Doppler secret snapshots are your high-availability solution to ensure applications can always access their secrets.&lt;/p&gt;

&lt;p&gt;Using Docker? Check out our dedicated &lt;a href="https://docs.doppler.com/docs/docker-high-availability"&gt;Docker High Availability documentation&lt;/a&gt; to learn more.&lt;/p&gt;

</description>
      <category>secretops</category>
      <category>kubernetes</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>State of Kubernetes Secrets Management in 2022</title>
      <dc:creator>Tiexin Guo</dc:creator>
      <pubDate>Wed, 23 Nov 2022 03:40:24 +0000</pubDate>
      <link>https://forem.com/doppler/state-of-kubernetes-secrets-management-in-2022-4bnd</link>
      <guid>https://forem.com/doppler/state-of-kubernetes-secrets-management-in-2022-4bnd</guid>
      <description>&lt;p&gt;Secrets are a fundamental building block of the modern Software Development Lifecycle (SDLC). Applications, CI/CD systems, API access, Databases, etc., all require some form of secrets/tokens/credentials. Keeping secrets out of source code, e.g. using the &lt;a href="https://12factor.net/"&gt;Twelve-Factor App methodology&lt;/a&gt;, is just one of many ways to ensure secrets are managed securely. &lt;/p&gt;

&lt;p&gt;While Kubernetes secrets are relatively simple to understand, there are many factors to consider when deciding how they will be managed and injected into containers.&lt;/p&gt;

&lt;p&gt;This blog post is a deep dive into the most popular approaches available for Kubernetes Secrets management in 2022, so without further adieu, let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Side Note: Enable Encryption of Kubernetes Secret Data
&lt;/h2&gt;

&lt;p&gt;Because Kubernetes Secrets are stored as unencrypted base64-encoded strings, it's strongly recommended to &lt;a href="https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/"&gt;enable encryption at rest for your cluster&lt;/a&gt; to provide additional protection in the case a malicious actor gains access to the underlying etcd data store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes Secrets Usage and Challenges
&lt;/h2&gt;

&lt;p&gt;Kubernetes essentially provides two methods for injecting secrets into a container:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secrets as files&lt;/li&gt;
&lt;li&gt;Secrets as environment variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application can then either read the file contents of the secrets (in the case of mounting secrets as data volumes) or consume them as environment variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secrets as Files
&lt;/h3&gt;

&lt;p&gt;Accessing secrets from the file system is common in frameworks such as ASP.NET Core and Java Spring Boot; however, some consider it an outdated approach in preference to environment variables.&lt;/p&gt;

&lt;p&gt;Because an application's config will likely vary between environments (e.g, development, staging, production, development), managing secrets in files pose several security questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Should you check the secret files in your version control system?

&lt;ul&gt;
&lt;li&gt;If so, how to encrypt them (since they are secrets)?&lt;/li&gt;
&lt;li&gt;If not, where to securely store the secret files?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Should secrets be stored in CI/CD and shipped with the deployment artifact?&lt;/li&gt;
&lt;li&gt;What if secret files are scattered across different repositories?&lt;/li&gt;
&lt;li&gt;How is access to secret files managed and audited?&lt;/li&gt;
&lt;li&gt;Is there a single source of truth for all secrets?&lt;/li&gt;
&lt;li&gt;What if the secrets files are language/framework-specific, but an app in another language/framework needs access to the same secrets?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kubernetes secrets can address most of these concerns by storing the contents of the secrets file in a single key and value and mounted as a single file inside a container.&lt;/p&gt;

&lt;p&gt;To mount a Kubernetes secret into a Pod's container within a deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-app&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-secrets&lt;/span&gt;
              &lt;span class="c1"&gt;# Mounted directory path in container&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/usr/src/app/secrets&lt;/span&gt;
              &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-secrets&lt;/span&gt;
          &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Kubernetes secret name&lt;/span&gt;
            &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet-webapp-appsettings&lt;/span&gt;
            &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="c1"&gt;# Key in secret containing appsettings.json contents&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;APP_SETTINGS&lt;/span&gt;
                &lt;span class="c1"&gt;# Results in file mounted at /usr/src/app/secrets/appsettings.json&lt;/span&gt;
                &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appsettings.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The downside is that most secret files are framework specific, so environment variables may be preferable for teams wanting a consistent approach to secrets injection.&lt;/p&gt;

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

&lt;p&gt;Environment variables from a Kubernetes secret are injected into a Pod's container process with precise control that allows for all or only a subset of secrets to be exposed.&lt;/p&gt;

&lt;p&gt;The most straightforward approach is to expose every secret key-value pair using the &lt;code&gt;envFrom&lt;/code&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-app&lt;/span&gt;
          &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Kubernetes secret name&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-secrets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or to only expose a specific list of key-value pairs, use the &lt;code&gt;env&lt;/code&gt; property with one or more &lt;code&gt;valueFrom&lt;/code&gt; items:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-app&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="c1"&gt;# Environment variable name&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MY_APP_SECRET&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="c1"&gt;# Kubernetes secret name&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;doppler-test-secret&lt;/span&gt;
                  &lt;span class="c1"&gt;# Key name in Kubernetes secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MY_APP_SECRET&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using environment variables for secrets comes with the benefit of not having to worry about teammates accidentally committing secrets to repositories. Unlike custom secret file formats like Java System Properties, environment variables are language and framework agnostic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes Secrets Source of Truth
&lt;/h2&gt;

&lt;p&gt;So you've decided Kubernetes Secrets are the way to go! Awesome! But because Kubernetes doesn't provide a great experience managing and updating secret values, we'll need to devise our own strategy.&lt;/p&gt;

&lt;p&gt;If we create Kubernetes Secrets using YAML files, where do we store those files? Encrypting them within a Git repository is one option. You'd then have the difficult task of managing encryption keys across different repositories and multiple environments and sharing secrets between teams with different needs and permissions. While tools such as &lt;a href="https://github.com/mozilla/sops"&gt;Mozilla SOPS&lt;/a&gt; and &lt;a href="https://github.com/bitnami-labs/sealed-secrets"&gt;Bitnami Sealed Secrets&lt;/a&gt; provide solutions for encrypted secrets, the operational overhead and complexity of managing secrets in version control is not the easiest solution to adopt and scale.&lt;/p&gt;

&lt;p&gt;Suppose we choose not to use encrypted YAML files and instead use Kubernetes Secrets as the single truth source. This simplifies things as secrets are only ever stored within Kubernetes, but it also presents new management and access control challenges.&lt;/p&gt;

&lt;p&gt;Imagine when you need to migrate to another Kubernetes cluster. You'd have to export and then import every secret, not to mention that multiple manual &lt;code&gt;kubectl&lt;/code&gt; commands are required when updating a secret, and we know that manual operations are error-prone.&lt;/p&gt;

&lt;p&gt;So, Kubernetes Secrets as the single source of truth is also a no-go.&lt;/p&gt;

&lt;p&gt;We need an external and centralized single source of truth with encrypted storage and fine-grained access controls for managing application secrets that can then be synced to Kubernetes via automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secrets Managers and Kubernetes
&lt;/h2&gt;

&lt;p&gt;Secret managers provide a secure and encrypted source of truth for secrets storage at enterprise scale.&lt;/p&gt;

&lt;p&gt;There are multiple choices on the market, such as Doppler, AWS Secrets Manager, HashiCorp Vault, etc. Although every secret manager has its quirks and features, they all meet the required security standards for storing and accessing sensitive data.&lt;/p&gt;

&lt;p&gt;Secrets Managers solve the security, permissions, sharings, single source of truth, and operational overhead issues once and for all, leaving only one challenge: how to synchronize secrets from the Secrets Manager to Kubernetes.&lt;/p&gt;

&lt;p&gt;Let's now look at the four most common secret sync approaches, working towards the ideal solution as we go.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Secrets Manager API or SDK
&lt;/h3&gt;

&lt;p&gt;With this method, you don't need to use Kubernetes Secrets, as they are fetched directly from the Secrets Manager via its API or SDK.&lt;/p&gt;

&lt;p&gt;The obvious downside is that this doesn't utilize Kubernetes Secrets at all. Plus, you are vendor locked-in (think of the tremendous overhead if you wanted to change to another Secrets Manager: you'd have to touch the code of all the apps).&lt;/p&gt;

&lt;p&gt;Kubernetes Secrets provide a native and vendor-agnostic method for applications to access secrets, so this is the least preferred option. &lt;/p&gt;

&lt;h3&gt;
  
  
  2. Secrets Agent/Sidecar Injection
&lt;/h3&gt;

&lt;p&gt;Using an agent or sidecar also accesses secrets at runtime via an API but provides a more native Kubernetes secrets access experience by dynamically mounting secrets into a Pod's container as part of the deployment process. It's emulating the same behavior as a Kubernetes mounted secret, but in a vendor-specific way and without actually using a Kubernetes Secret. Let's use HashiCorp Vault as an example to illustrate.&lt;/p&gt;

&lt;p&gt;The HashiCorp Vault has an Agent Injector that dynamically alters pod specifications during deployment to include Vault Agent containers that render Vault secrets to a shared memory volume using &lt;a href="https://www.vaultproject.io/docs/agent/template"&gt;Vault Agent Templates&lt;/a&gt; (phew!). The injector is a &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/"&gt;Kubernetes Mutation Webhook Controller&lt;/a&gt;. The controller intercepts pod events and applies mutations to the Pod if annotations exist within the request. This functionality is provided by the &lt;a href="https://github.com/hashicorp/vault-Kubernetes"&gt;vault-Kubernetes&lt;/a&gt; project. For more info, refer to the &lt;a href="https://www.vaultproject.io/docs/platform/Kubernetes/injector"&gt;Vault Kubernetes Injector documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By rendering secrets to a shared volume, containers within the Pod can consume Vault secrets without being Vault-aware.&lt;/p&gt;

&lt;p&gt;There are, however, a few downsides. The first and most significant is that installing and using this method requires in-depth knowledge of Kubernetes and HashiCorp Vault. The beauty of Kubernetes Secrets is that they're simple to understand and consume. In the quest to integrate a Secrets Manager more tightly into the application deployment process, we now have something much more complex to wrap our heads around.&lt;/p&gt;

&lt;p&gt;The second is understanding how the agent injects the secrets as a volume. Every container in the Pod can be configured to mount a shared memory volume. This volume is mounted to &lt;code&gt;/vault/secrets&lt;/code&gt;, meaning the containerized app must read its secrets from files, losing the benefits of environment variable injection.&lt;/p&gt;

&lt;p&gt;Third, for the injection to work, the user must add various Vault-specific secret injection annotations, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;vault.hashicorp.com/agent-inject-secret-foo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;database/roles/app&lt;/span&gt;
&lt;span class="na"&gt;vault.hashicorp.com/agent-inject-secret-bar&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;consul/creds/app&lt;/span&gt;
&lt;span class="na"&gt;vault.hashicorp.com/role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;app'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first annotation will be rendered to &lt;code&gt;/vault/secrets/foo&lt;/code&gt; with the second annotation rendered to &lt;code&gt;/vault/secrets/bar&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And this brings operational overhead as you'll need to update all your Deployments' annotation. Also, you are vendor locked in now, and if you want to move to another secret manager, you'd have to rework all of your Deployment manifests.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Kubernetes Secrets Operators
&lt;/h3&gt;

&lt;p&gt;A Kubernetes Operator is a specific type of application designed to extend the functionality of Kubernetes, such as the &lt;a href="https://docs.doppler.com/docs/kubernetes-operator"&gt;Doppler Secrets Operator&lt;/a&gt; and &lt;a href="https://github.com/external-secrets/external-secrets"&gt;External Secrets Operator&lt;/a&gt;. Here, we'll use the &lt;a href="https://github.com/external-secrets/external-secrets"&gt;External Secrets Operator&lt;/a&gt; to show how to add new secrets sync functionality to your Kubernetes Cluster.&lt;/p&gt;

&lt;p&gt;The External Secrets Operator integrates external secret managers such as Doppler, AWS Secrets Manager, HashiCorp Vault, Google Secrets Manager, Azure Key Vault, and many more. The operator is configured to sync specific secrets from one or more Secrets Managers to various Kubernetes Secrets while continuously syncing any secret changes on a set interval.&lt;/p&gt;

&lt;p&gt;The Kubernetes External Secrets Operator has many advantages over the previous methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It supports multiple secrets managers with no vendor-lockin.&lt;/li&gt;
&lt;li&gt;It syncs secrets to plain old Kubernetes Secrets, so there is no need to update your Deployment manifests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does introduce additional operational overhead in that even though you don't need to maintain Kubernetes Secrets anymore, you do have to manage the new Kubernetes objects specific to the External Secrets Operator.&lt;/p&gt;

&lt;p&gt;The External Secrets Operator works by using a &lt;code&gt;SecretStore&lt;/code&gt; or &lt;code&gt;ClusterSecretStore&lt;/code&gt; to provide authenticated access to a secrets manager and an &lt;code&gt;ExternalSecret&lt;/code&gt; that references one of these stores and controls how and which secrets are synced to Kubernetes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external-secrets.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretStore&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;doppler-auth-api&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;doppler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;secretRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;dopplerToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;doppler-token-auth-api&lt;/span&gt;
            &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dopplerToken&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external-secrets.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ExternalSecret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;auth-api-all&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;secretStoreRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretStore&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;doppler-auth-api&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;auth-api-all&lt;/span&gt;
  &lt;span class="na"&gt;dataFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;find&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;regexp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much better than maintaining native Kubernetes Secrets YAML and is significantly more straightforward than the Secrets Agent/Sidecar Injection method.&lt;/p&gt;

&lt;p&gt;We'll also be covering the Doppler Secrets Operator later in this article.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Secrets Store Container Storage Interface (CSI)
&lt;/h3&gt;

&lt;p&gt;A relatively new and emerging method uses a &lt;a href="https://secrets-store-csi-driver.sigs.k8s.io/"&gt;Kubernetes Secrets Store CSI Driver&lt;/a&gt;, which allows secrets stored in secret managers to be mounted into pods as a volume. Once the Volume is attached to the Pod, the data is mounted into the container's file system.&lt;/p&gt;

&lt;p&gt;While this seems similar to the Agent/Sidecar method where you'd still have to read files, Secrets Store CSI also supports synchronizing secrets as native Kubernetes Secrets with an extra &lt;code&gt;SecretProviderClass&lt;/code&gt; custom resource, allowing for secrets to be injected as environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secrets-store.csi.x-Kubernetes.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretProviderClass&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-provider&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# accepted provider options: Azure, Vault, or GCP&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vault&lt;/span&gt;
  &lt;span class="c1"&gt;# [OPTIONAL] SecretObject defines the desired state of synced Kubernetes secret objects&lt;/span&gt;
  &lt;span class="na"&gt;secretObjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# data field to populate&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;username&lt;/span&gt;
      &lt;span class="c1"&gt;# name of the mounted content to sync. this could be the object name or the object alias&lt;/span&gt;
      &lt;span class="na"&gt;objectName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;foo1&lt;/span&gt;
    &lt;span class="c1"&gt;# name of the Kubernetes Secret object&lt;/span&gt;
    &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;foosecret&lt;/span&gt;
    &lt;span class="c1"&gt;# type of the Kubernetes Secret object e.g., Opaque, kubernetes.io/tls&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Opaque&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the same level of operational overhead as the External Secrets Operator, as you need to manage one YAML per secret.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto-Updating Deployments When Secrets Change
&lt;/h2&gt;

&lt;p&gt;Now that we've covered the most common methods for syncing secrets to Kubernetes, let's get to a significant pain point. When a secret value changes, how do you reload the Kubernetes Deployments consuming those secrets?&lt;/p&gt;

&lt;p&gt;The Secrets Store CSI Driver can periodically update the Pod mount and Kubernetes Secret with the latest content from external secrets managers (e.g., auto rotation of database credentials). However, the CSI driver does not trigger a reload for the affected deployments—it only handles updating the Pod mount and Kubernetes Secret, similar to how Kubernetes updates secrets mounted as volumes.&lt;/p&gt;

&lt;p&gt;Unfortunately, there isn't one out-of-the-box, elegant solution for triggering deployments to be reloaded with the solutions we've shown. You'd need to rely on open source tools such as &lt;a href="https://github.com/stakater/Reloader"&gt;Reloader&lt;/a&gt;, which watches for changes in ConfigMap and Secrets and does rolling upgrades on Pods with their associated Deployment, StatefulSet, DaemonSet, and DeploymentConfig.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes Secrets Management Essentials for 2023 and beyond
&lt;/h2&gt;

&lt;p&gt;It's now easier than ever to effectively manage Kubernetes secrets, and whether you're just about to move your workloads to Kubernetes or you're revisiting how to manage secrets, here's the list of features that I'd prioritize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to use, intuitive secrets management experience.&lt;/li&gt;
&lt;li&gt;Easy to sync secrets to Kubernetes Secrets without a complex setup and installation process.&lt;/li&gt;
&lt;li&gt;Environment variables for application config and secrets.&lt;/li&gt;
&lt;li&gt;Low operational overhead that makes best practices easy to implement.&lt;/li&gt;
&lt;li&gt;Multi-cloud capabilities to avoid vendor lock-in and &lt;a href="https://blog.doppler.com/siloed-secrets-and-security-theatre-why-we-need-secretops"&gt;siloed secrets&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Last but not least, the killer feature: automatically reload a Deployment/Pod when secrets change.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the Doppler SecretOps Platform Could Change the Game
&lt;/h2&gt;

&lt;p&gt;Because &lt;a href="https://blog.doppler.com/why-secrets-management-is-not-a-key-value-store"&gt;traditional secrets managers are just key-value stores&lt;/a&gt; (e.g. HashiCorp Vault and AWS Secrets Manager), most teams still have a frustrating experience when it comes to managing secrets. &lt;/p&gt;

&lt;p&gt;Managing and syncing secrets must be as easy as possible because as friction and difficulty increase, so does the number of developers and teams that implement their own (and likely insecure) solution instead.&lt;/p&gt;

&lt;p&gt;A Secrets Manager like this should've existed years ago, but now, a new kid is on the block: &lt;a href="https://www.doppler.com/"&gt;Doppler&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Simply put, Doppler is trying to make it as easy as it should be to manage secrets on Kubernetes thanks to the &lt;a href="https://docs.doppler.com/docs/kubernetes-operator"&gt;Doppler Secrets Operator&lt;/a&gt;. Easy to use? Check. Easy to sync secrets as Kubernetes Secrets? Check. Twelve-Factor App, cloud-native? Check. No overhead? Check. &lt;/p&gt;

&lt;p&gt;And the big one: Automatic reload upon secrets update? Check! And it only takes a few minutes to install and configure:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/763876554" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;If you're interested in trying Doppler, you can &lt;a href="https://medium.com/4th-coffee/doppler-a-brief-introduction-to-secrets-managers-e779b48fac1b"&gt;check out my quick tutorial&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;The evolving Kubernetes Secrets landscape now provides development teams with many choices for storing, managing, syncing, and injecting secrets into containers.&lt;/p&gt;

&lt;p&gt;I hope this post has given you some new ideas for improving your secrets management workflows in Kubernetes.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>devsecop</category>
      <category>kubernetes</category>
      <category>security</category>
    </item>
    <item>
      <title>Managing Kubernetes Secrets with the External Secrets Operator and Doppler</title>
      <dc:creator>Tyler Langlois</dc:creator>
      <pubDate>Tue, 25 Oct 2022 15:48:28 +0000</pubDate>
      <link>https://forem.com/doppler/managing-kubernetes-secrets-with-the-external-secrets-operator-and-doppler-h90</link>
      <guid>https://forem.com/doppler/managing-kubernetes-secrets-with-the-external-secrets-operator-and-doppler-h90</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--I9XAfdn5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o5hzbzxxhk2psd0fqto6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I9XAfdn5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o5hzbzxxhk2psd0fqto6.jpg" alt="Cover" width="880" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;API keys, credentials, and other types of sensitive information are the primary way your application calls external services and interacts with the world outside its own runtime. Ensuring that your secrets are secure is one of the most important operational tasks that you need to address, but it's not just rigorous security that's important providing an ergonomic, audited, and maintainable flow for the &lt;em&gt;management&lt;/em&gt; of those secrets is crucial as well. If managing credentials is confusing or difficult, mistakes can undo all of the careful work that goes into keeping secrets secure.&lt;/p&gt;

&lt;p&gt;Container orchestrators like Kubernetes offer helpful abstractions to address the need for sensitive values with native &lt;a href="https://kubernetes.io/docs/concepts/configuration/secret/"&gt;Secrets&lt;/a&gt;. However, a Kubernetes &lt;code&gt;Secret&lt;/code&gt; is a somewhat rudimentary object it lacks encryption by default and normally exists as a plain key/value within &lt;code&gt;etcd&lt;/code&gt;. By contrast, services like &lt;a href="https://www.vaultproject.io/"&gt;Vault&lt;/a&gt; or &lt;a href="https://www.doppler.com/"&gt;Doppler&lt;/a&gt; are intentionally designed to provide strong guarantees and well-defined access controls. Can we combine the operational power of Kubernetes with the assurance of a fully managed secret provider?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--v4Ic_ivU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1666646955334/WHLpfqRIr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v4Ic_ivU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1666646955334/WHLpfqRIr.png" alt="Doppler Kubernetes External Secrets Operator" width="880" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes! The &lt;a href="https://github.com/external-secrets/external-secrets"&gt;External Secrets Operator&lt;/a&gt; is a Kubernetes &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/operator/"&gt;operator&lt;/a&gt; that bridges the gap between Kubernetes' native secret support and &lt;em&gt;external&lt;/em&gt; systems that provide a canonical source of truth for secret storage. It does this by leveraging &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/"&gt;custom resources&lt;/a&gt; that define how to retrieve external secrets in order to manage the lifecycle of &lt;code&gt;Secret&lt;/code&gt; resources in your Kubernetes cluster. In this tutorial, we'll use &lt;a href="https://www.doppler.com/"&gt;Doppler&lt;/a&gt; as the external secret provider to illustrate using external secrets in a real-world application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outcomes
&lt;/h2&gt;

&lt;p&gt;The end goal of this guide will be to leverage well-managed secrets in an application deployed in Kubernetes. To achieve this, we'll use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Kubernetes &lt;a href="https://github.com/external-secrets/external-secrets"&gt;External Secrets Operator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.doppler.com/"&gt;Doppler&lt;/a&gt; to store and manage external secrets&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://minikube.sigs.k8s.io/docs/"&gt;minikube&lt;/a&gt; or &lt;a href="https://kind.sigs.k8s.io/"&gt;kind&lt;/a&gt; for a local Kubernetes API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the conclusion of this tutorial, you'll understand how to store and manage secrets in Doppler, use those same secrets as native &lt;em&gt;Kubernetes&lt;/em&gt; secrets, and ultimately use them in a running application. Let's begin!&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;This article assumes that you'll follow along with your own Kubernetes cluster. You may choose to operate in an existing cluster (potentially within a sandboxed namespace), but if you'd like to use a sandbox instead, we suggest using either &lt;a href="https://minikube.sigs.k8s.io/docs/"&gt;minikube&lt;/a&gt; or &lt;a href="https://kind.sigs.k8s.io/"&gt;kind&lt;/a&gt; to create a local Kubernetes installation which will provide a safe environment for testing. In order to provide a clean and pristine environment, this tutorial will use &lt;a href="https://minikube.sigs.k8s.io/docs/"&gt;minikube&lt;/a&gt; as the Kubernetes target.&lt;/p&gt;

&lt;p&gt;In addition to a functional Kubernetes cluster, ensure that the following command-line tools are installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;helm&lt;/code&gt;, which we'll use to install the External Secrets Operator Kubernetes resources. Use the helm &lt;a href="https://helm.sh/docs/intro/quickstart/"&gt;quickstart guide&lt;/a&gt; to install helm.&lt;/li&gt;
&lt;li&gt;The Doppler command line utility &lt;code&gt;doppler&lt;/code&gt; using the Doppler &lt;a href="https://docs.doppler.com/docs/cli"&gt;CLI Guide&lt;/a&gt; documentation.

&lt;ul&gt;
&lt;li&gt;You will need to &lt;a href="https://dashboard.doppler.com/register"&gt;register for a Doppler account&lt;/a&gt; to create projects and store external secrets.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubectl&lt;/code&gt; to interact with Kubernetes. &lt;code&gt;kubectl&lt;/code&gt; is available for a wide range of operating systems, and the &lt;a href="https://kubernetes.io/docs/tasks/tools/"&gt;Kubernetes documentation&lt;/a&gt; provides comprehensive installation guides for &lt;code&gt;kubectl&lt;/code&gt; and other tools as well.&lt;/li&gt;
&lt;li&gt;If you choose to use a local Kubernetes sandbox, install &lt;a href="https://minikube.sigs.k8s.io/docs/"&gt;minikube&lt;/a&gt; and follow the instructions to create a local instance of Kubernetes. This can be as simple as &lt;code&gt;minikube start&lt;/code&gt;, but consult the documentation if you need additional assistance.

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;minikube&lt;/code&gt; bundles a version-compatible installation of &lt;code&gt;kubectl&lt;/code&gt; if you choose to use &lt;code&gt;minikube&lt;/code&gt; for this exercise. It's a handy time saver.&lt;/li&gt;
&lt;/ul&gt;


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

&lt;p&gt;Once installed, confirm that your environment is ready to go:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl version&lt;/code&gt; should confirm that &lt;code&gt;kubectl&lt;/code&gt; is present and communicating with the Kubernetes API successfully. If you aren't using &lt;code&gt;minikube&lt;/code&gt;, simply use the plain &lt;code&gt;kubectl&lt;/code&gt; command for the remainder of the tutorial.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ minikube kubectl -- version
Client Version: version.Info{Major:"1", Minor:"25", GitVersion:"v1.25.0", GitCommit:"a866cbe2e5bbaa01cfd5e969aa3e033f3282a8a2", GitTreeState:"clean", BuildDate:"2022-08-23T17:44:59Z", GoVersion:"go1.19", Compiler:"gc", Platform:"linux/amd64"}
Kustomize Version: v4.5.7
Server Version: version.Info{Major:"1", Minor:"25", GitVersion:"v1.25.0", GitCommit:"a866cbe2e5bbaa01cfd5e969aa3e033f3282a8a2", GitTreeState:"clean", BuildDate:"2022-08-23T17:38:15Z", GoVersion:"go1.19", Compiler:"gc", Platform:"linux/amd64"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;helm&lt;/code&gt; and &lt;code&gt;doppler&lt;/code&gt; commands should be available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ helm version
version.BuildInfo{Version:"v3.9.0", GitCommit:"7ceeda6c585217a19a1131663d8cd1f7d641b2a7", GitTreeState:"", GoVersion:"go1.17.13"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ doppler --version
v3.44.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the prerequisites installed, let's proceed with building and running our program.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample Application
&lt;/h2&gt;

&lt;p&gt;Before we start creating secrets, let's begin by illustrating the external secrets workflow with a simple example service. The following guide will assume the use of &lt;code&gt;minikube&lt;/code&gt; to build and load an application container image, so if your Kubernetes environment is different, you may need to adapt the instructions to work with your specific container registry strategy.&lt;/p&gt;

&lt;p&gt;First, perform some initial steps to bootstrap a python &lt;a href="https://flask.palletsprojects.com/en/2.2.x/"&gt;Flask&lt;/a&gt; application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mkdir -p ~/tmp/secret-sauce
$ cd ~/tmp/secret-sauce
$ git init
$ echo flask &amp;gt; requirements.txt
$ python3 -m venv venv
$ ./venv/bin/pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a file named &lt;code&gt;app.py&lt;/code&gt; that contains the following simple web application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from flask import Flask
from os import environ

app = Flask( __name__ )

sauce = environ.get('SECRET_SAUCE')

@app.route("/")
def index():
    if sauce:
        return f"The secret sauce is: {sauce}!"
    else:
        return "You'll never find my secret sauce."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Define a small &lt;code&gt;Dockerfile&lt;/code&gt; that defines how to build a container image for our application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM python:3

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["python", "-m" , "flask", "run", "--host=0.0.0.0"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can run this application locally with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./venv/bin/flask run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try accessing the running application at &lt;a href="http://localhost:5000"&gt;http://localhost:5000&lt;/a&gt; to see a response. The root route (&lt;code&gt;/&lt;/code&gt;) will render a static string when no secret is present in the environment but will print the secret when the indicated environment variable is defined. Don't print secrets in production! This application is just a demonstration of how to read the value in a real program.&lt;/p&gt;

&lt;p&gt;We're ready to load this application into Kubernetes. Assuming that you're following along with &lt;code&gt;minikube&lt;/code&gt;, proceed to build the application container in the Kubernetes node under the name "&lt;code&gt;app&lt;/code&gt;":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ minikube image build -t app .
...lots of output...
Successfully tagged app:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new Kubernetes &lt;code&gt;Deployment&lt;/code&gt; file named &lt;code&gt;deployment.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flask
  template:
    metadata:
      labels:
        app: flask
    spec:
      containers:
        - name: webapp
          image: app
          imagePullPolicy: Never
          ports:
            - containerPort: 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take note of a few assumptions in this &lt;code&gt;Deployment&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;imagePullPolicy&lt;/code&gt; has been set to &lt;code&gt;Never&lt;/code&gt; because we're running a locally-built image. By default, Kubernetes would attempt to &lt;code&gt;pull&lt;/code&gt; the &lt;code&gt;app&lt;/code&gt; image, which doesn't exist. As previously mentioned, if you're following this tutorial in an environment other than &lt;code&gt;minikube&lt;/code&gt;, you may need to push the container image to a registry and adjust these settings slightly.&lt;/li&gt;
&lt;li&gt;We've exposed port &lt;code&gt;:5000&lt;/code&gt; which we can access later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Load this &lt;code&gt;Deployment&lt;/code&gt; into your running cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl apply -f deployment.yaml
deployment.apps/app created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, forward the port in another terminal window in order to access the running application in a simple tunnel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl port-forward deployment/app 5000:5000
Forwarding from 127.0.0.1:5000 -&amp;gt; 5000
Forwarding from [::1]:5000 -&amp;gt; 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send a request to your Kubernetes application to see it in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl http://localhost:5000
You'll never find my secret sauce.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fantastic! Note that we've received the response that indicates no secret value has been injected into the environment. How can we add a secret to the application and see the &lt;em&gt;secret sauce&lt;/em&gt;?&lt;/p&gt;

&lt;h2&gt;
  
  
  Doppler
&lt;/h2&gt;

&lt;p&gt;By using &lt;a href="https://www.doppler.com/"&gt;Doppler&lt;/a&gt;, we can achieve a great deal of control over application secrets and manage them at each step of our application's lifecycle, from in our local development environment to within a Kubernetes workload.&lt;/p&gt;

&lt;p&gt;If you haven't signed up with Doppler, you can &lt;a href="https://dashboard.doppler.com/register"&gt;do so now&lt;/a&gt;. Once you have an account, proceed to use the &lt;code&gt;login&lt;/code&gt; command to set up your account locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ doppler login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setup Doppler for the demo application by defining a &lt;code&gt;doppler-template.yaml&lt;/code&gt; file in the root of the application directory. This YAML file defines a template that we can import to create a new Doppler project easily from the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;projects:
  - name: secret-sauce
    description: Kubernetes demo app
    environments:
      - slug: dev
        name: Development
        configs:
          - slug: dev
      - slug: stg
        name: Staging
        configs:
          - slug: stg
      - slug: prd
        name: Production
        configs:
          - slug: prd
    secrets:
      dev:
        SECRET_SAUCE: tartar
      stg:
        SECRET_SAUCE: horseradish
      prd:
        SECRET_SAUCE: tzatziki
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enter the directory in your shell and run the following &lt;code&gt;doppler&lt;/code&gt; command in order to bootstrap your Doppler project. This will create a new project called &lt;code&gt;secret-sauce&lt;/code&gt;, set up a few different environments, and load an initial secret value for &lt;code&gt;SECRET_SAUCE&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ doppler import
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see output similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────┬──────────────┬─────────────────────┬──────────────────────────┐
│ ID           │ NAME         │ DESCRIPTION         │ CREATED AT               │
├──────────────┼──────────────┼─────────────────────┼──────────────────────────┤
│ secret-sauce │ secret-sauce │ Kubernetes demo app │ 2022-10-11T22:10:36.567Z │
└──────────────┴──────────────┴─────────────────────┴──────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;View the secrets for this project with &lt;code&gt;doppler secrets&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ doppler secrets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to some default variables that begin with &lt;code&gt;DOPPLER_&lt;/code&gt;, you'll also find the secret value we'd like to inject, &lt;code&gt;SECRET_SAUCE&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────┬──────────────┐
│ NAME                │ VALUE        │
├─────────────────────┼──────────────┤
│ DOPPLER_CONFIG      │ dev          │
│ DOPPLER_ENVIRONMENT │ dev          │
│ DOPPLER_PROJECT     │ secret-sauce │
│ SECRET_SAUCE        │ tartar       │
└─────────────────────┴──────────────┘   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run a quick test to confirm that our application behaves as expected when a secret is present in its environment variables. To do this, we can use the &lt;code&gt;doppler run&lt;/code&gt; command to easily inject the variable into our application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ doppler run -- ./venv/bin/flask run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then issue a request to the listening port to see whether the response has changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl http://localhost:5000
The secret sauce is: tartar!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! Let's continue on to install the External Secrets Operator.&lt;/p&gt;

&lt;h2&gt;
  
  
  External Secrets
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/external-secrets/external-secrets"&gt;External Secrets Operator&lt;/a&gt; provides the translation layer between Kubernetes' native secrets and external secrets. The operator leverages &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/"&gt;custom resources&lt;/a&gt; in order to model external secrets, which it then retrieves as necessary and translates into native Kubernetes secrets that your workload can easily consume.&lt;/p&gt;

&lt;p&gt;The operator is provided as a Helm chart, so first add the upstream &lt;code&gt;external-secrets&lt;/code&gt; Helm repository to access the chart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ helm repo add external-secrets https://charts.external-secrets.io
external-secrets has been added to your repositories
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, install the chart into your Kubernetes cluster. The following command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instructs &lt;code&gt;helm&lt;/code&gt; to use the &lt;code&gt;external-secrets&lt;/code&gt; chart,&lt;/li&gt;
&lt;li&gt;passes &lt;code&gt;-n&lt;/code&gt; to install the chart into the &lt;code&gt;external-secrets&lt;/code&gt; namespace,&lt;/li&gt;
&lt;li&gt;creates the namespace as necessary with &lt;code&gt;--create-namespace&lt;/code&gt;, and&lt;/li&gt;
&lt;li&gt;configures the requisite CRDs as well (&lt;code&gt;--set installCRDs=true&lt;/code&gt;)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ helm install external-secrets \
     external-secrets/external-secrets \
     -n external-secrets \
     --create-namespace \
     --set installCRDs=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME: external-secrets
LAST DEPLOYED: Tue Oct 11 12:27:18 2022
NAMESPACE: external-secrets
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
external-secrets has been deployed successfully!

In order to begin using ExternalSecrets, you will need to set up a SecretStore
or ClusterSecretStore resource (for example, by creating a 'vault' SecretStore).

More information on the different types of SecretStores and how to configure them
can be found in our Github: https://github.com/external-secrets/external-secrets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this operator installed, we're now ready to set up Doppler to serve as the source for external secrets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating Doppler with Kubernetes
&lt;/h2&gt;

&lt;p&gt;The first step is to create a &lt;a href="////docs.doppler.com/docs/service-tokens"&gt;service token&lt;/a&gt; which serves as the authentication mechanism for the external secrets operator against Doppler. It should be stored inside of a generic Kubernetes secret that the operator will consume.&lt;/p&gt;

&lt;p&gt;You can generate and store the token in a single step with some fancy footwork in the shell to avoid copying and pasting the token around. The following command creates a new Doppler token and then immediately loads it into Kubernetes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl create secret generic \
    doppler-token-auth-api \
    --from-literal dopplerToken=$(doppler configs tokens create --config prd doppler-auth-token --plain)
secret/doppler-token-auth-api created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we're passing the &lt;code&gt;--config prd&lt;/code&gt; flag to &lt;code&gt;doppler&lt;/code&gt; in order to create a token scoped to the &lt;code&gt;prd&lt;/code&gt; (production) configuration of our Doppler project. In our application directory, we defaulted to &lt;code&gt;dev&lt;/code&gt;, which has its own secrets. With this technique, we're only interacting with development secrets locally while loading production secrets into our container runtime, which keeps the risk of exposure for production secrets minimal.&lt;/p&gt;

&lt;p&gt;Next, create a &lt;code&gt;SecretStore&lt;/code&gt; CRD that points the operator at your Doppler service token secret. This step sets up Doppler as a source for external secrets that we can call upon later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: doppler-auth-api
spec:
  provider:
    doppler:
      auth:
        secretRef:
          dopplerToken:
            name: doppler-token-auth-api
            key: dopplerToken
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the &lt;code&gt;SecretStore&lt;/code&gt; in Kubernetes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl apply -f secretstore.yaml
secretstore.external-secrets.io/doppler-auth-api created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're ready to inject our secret sauce! A new &lt;code&gt;ExternalSecret&lt;/code&gt; is the necessary resource that enables us to synchronize one of our &lt;em&gt;Doppler&lt;/em&gt; secrets to a related generic &lt;em&gt;Kubernetes&lt;/em&gt; secret. Create a new file called &lt;code&gt;externalsecret.yaml&lt;/code&gt; with the following YAML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: secret-sauce
spec:
  secretStoreRef:
    kind: SecretStore
    name: doppler-auth-api

  target:
    name: secret-sauce

  data:
    - secretKey: SECRET_SAUCE
      remoteRef:
        key: SECRET_SAUCE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Load it into Kubernetes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl apply -f externalsecret.yaml
externalsecret.external-secrets.io/secret-sauce created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can confirm that the secret was loaded by the operator by listing it with &lt;code&gt;kubectl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl get secret secret-sauce
NAME TYPE DATA AGE
secret-sauce Opaque 1 3s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Success! Note that if you do not see the new secret, you can also inspect the External Secret Operator's logs to debug any issues:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl -n external-secrets logs -lapp.kubernetes.io/name=external-secrets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generic secrets can be seen in the Kubernetes dashboard as well. If you're using &lt;code&gt;minikube&lt;/code&gt;, you can quickly install and view the Kubernetes dashboard with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ minikube dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your browser will open to the dashboard landing page. You may use the left sidebar to navigate to the "Secrets" link to view your new secret:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UZ52PoAB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1666642558970/mVLa_og-O.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UZ52PoAB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1666642558970/mVLa_og-O.png" alt="Kubernetes Dashboard" width="880" height="644"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The only task left to do is to consume the secret from our application. Here's an updated &lt;code&gt;Deployment&lt;/code&gt; that references the newly-created &lt;code&gt;Secret&lt;/code&gt;. Note the new &lt;code&gt;env&lt;/code&gt; key, which injects the variable &lt;code&gt;SECRET_SAUCE&lt;/code&gt; drawn from a secret named &lt;code&gt;secret-sauce&lt;/code&gt; under the key &lt;code&gt;SECRET_SAUCE&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flask
  template:
    metadata:
      labels:
        app: flask
    spec:
      containers:
        - name: webapp
          image: app
          imagePullPolicy: Never
          env:
            - name: SECRET_SAUCE
              valueFrom:
                secretKeyRef:
                  name: secret-sauce
                  key: SECRET_SAUCE
          ports:
            - containerPort: 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply the updated &lt;code&gt;Deployment&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl apply -f deployment.yaml
deployment.apps/app configured
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The changed &lt;code&gt;Deployment&lt;/code&gt; manifest will recreate any necessary pods, so invoke a new forwarded port once the pods have restarted to forward requests to the new containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl port-forward deployment/app 5000:5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, Issue an HTTP request to the running container to see the injected Doppler secret in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl http://localhost:5000
The secret sauce is: tzatziki!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the content of this secret differs from the one we saw in our local development environment. The &lt;code&gt;--config prd&lt;/code&gt; flag to the &lt;code&gt;doppler&lt;/code&gt; command loaded a token with rights to the &lt;code&gt;prd&lt;/code&gt; configuration in Doppler, which injects the correct secret for the configuration that the Doppler token has been scoped to.&lt;/p&gt;

&lt;p&gt;Congratulations! You've successfully:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Installed the External Secrets Operator into your Kubernetes cluster&lt;/li&gt;
&lt;li&gt;Managed an application secret with Doppler&lt;/li&gt;
&lt;li&gt;Connected Doppler with the External Secrets Operator&lt;/li&gt;
&lt;li&gt;Used a Doppler project secret within a Kubernetes Deployment&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;There's even more you can do with the External Secrets Operator and Doppler, so if you'd like to learn more, dive into the &lt;a href="https://external-secrets.io/latest/provider/doppler/"&gt;documentation&lt;/a&gt; to learn how to use features like JSON processing, filtering, and more.&lt;/p&gt;

&lt;p&gt;If you're actively using Kubernetes secrets stored in &lt;code&gt;etcd&lt;/code&gt; today, you should also &lt;a href="https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/"&gt;configure encryption at rest&lt;/a&gt; so that your secrets are secure no matter where they're stored. Whether you load them with &lt;code&gt;kubectl&lt;/code&gt; or the External Secrets Operator, configuring encryption at rest is still an important step to address every step of your secret management lifecycle.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How To Prevent Secrets From Ending Up On Developers' Machines</title>
      <dc:creator>Ryan Blunden</dc:creator>
      <pubDate>Tue, 21 Jun 2022 00:28:05 +0000</pubDate>
      <link>https://forem.com/doppler/how-to-prevent-secrets-from-ending-up-on-developers-machines-40cb</link>
      <guid>https://forem.com/doppler/how-to-prevent-secrets-from-ending-up-on-developers-machines-40cb</guid>
      <description>&lt;p&gt;Even with environment variable storage offered by modern hosting platforms and secrets managers provided by every cloud, developers' machines are still littered with secrets in unencrypted text files because local development was left out of the picture.&lt;/p&gt;

&lt;p&gt;But we can remedy this situation using dynamic secrets injection and ephemeral secrets files, and in this post, we'll be using the &lt;a href="https://docs.doppler.com/docs/accessing-secrets"&gt;Doppler CLI&lt;/a&gt; to demonstrate how this is possible.&lt;/p&gt;

&lt;p&gt;But before diving in, we need to solve the problem of secure and encrypted storage for secrets used in local development.&lt;/p&gt;

&lt;h2&gt;
  
  
  SecretOps and Local Development
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AoNHev2p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ekavxmtuelypomp0wp7u.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AoNHev2p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ekavxmtuelypomp0wp7u.jpeg" alt="Doppler SecretOps Platform" width="880" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Development scoped secrets simply don't exist in traditional solutions because &lt;a href="https://blog.doppler.com/siloed-secrets-and-security-theatre-why-we-need-secretops"&gt;secrets are siloed&lt;/a&gt; within the confines of their respective cloud or platform.&lt;/p&gt;

&lt;p&gt;While multi-cloud capable secret managers such as &lt;a href="https://blog.doppler.com/doppler-vs-hashicorp-vault"&gt;HashiCorp Vault&lt;/a&gt; showed great promise, the prohibitively steep learning curve and unavoidable complexity involved with fetching secrets create a significant barrier to adoption as teams are left with no incentive to switch.&lt;/p&gt;

&lt;p&gt;A SecretOps Platform builds upon the idea of centralized secrets storage but differs from existing solutions by providing a CLI and integrations for syncing secrets to any environment, machine, platform, or cloud secrets manager. It's the best of both worlds, providing a single source of truth for management while development teams choose the best secrets access method on a per-application basis.&lt;/p&gt;

&lt;p&gt;How does this help local development? In a SecretOps world, each application has a &lt;strong&gt;Development&lt;/strong&gt; environment specifically for use on developers' machines, solving the storage problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CDbAr_Z5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1654745217584/uRYxiX7It.jpg%2520align%3D%2522left%2522" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CDbAr_Z5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1654745217584/uRYxiX7It.jpg%2520align%3D%2522left%2522" alt="doppler-project.jpg" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using the &lt;a href="https://docs.doppler.com/docs/install-cli"&gt;Doppler CLI&lt;/a&gt; to demonstrate, let's dive in to explore the mechanics of dynamic secrets injection and ephemeral secrets files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic Secrets Injection
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.doppler.com/docs/install-cli"&gt;Doppler CLI&lt;/a&gt; uses the same secrets injection model as platforms such as Heroku and Cloudflare Workers by injecting the secrets as environment variables into the application process.&lt;/p&gt;

&lt;p&gt;You can use a command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler run &lt;span class="nt"&gt;--&lt;/span&gt; npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler run &lt;span class="nt"&gt;--&lt;/span&gt; ./launch-app.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a long-running background process in a virtual machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;nohup &lt;/span&gt;doppler run &lt;span class="nt"&gt;--&lt;/span&gt; npm start &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the Doppler CLI inside a Docker container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;…
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu&lt;/span&gt;

&lt;span class="c"&gt;# Install Doppler CLI&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-Ls&lt;/span&gt; &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.2 &lt;span class="nt"&gt;--proto&lt;/span&gt; &lt;span class="s2"&gt;"=https"&lt;/span&gt; &lt;span class="nt"&gt;--retry&lt;/span&gt; 3 https://cli.doppler.com/install.sh | sh

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["doppler", "run", "--", "npm", "start"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And inject environment variables consumed by Docker Compose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler run &lt;span class="nt"&gt;--&lt;/span&gt; docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's also possible to pipe secrets in .env file format to the Docker CLI, where it reads the output as a file using &lt;a href="https://tldp.org/LDP/abs/html/process-sub.html"&gt;process substitution&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env-file&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;doppler secrets download &lt;span class="nt"&gt;--no-file&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; docker&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  my-awesome-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same technique applies to creating a Kubernetes secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create secret generic my-app-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-env-file&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;doppler secrets download &lt;span class="nt"&gt;--no-file&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; docker&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if a secrets file is what your application needs, we've got you covered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ephemeral Secrets Files
&lt;/h2&gt;

&lt;p&gt;The Doppler CLI enables the mounting of ephemeral secrets files in .env, JSON, or a custom file format using a secrets template that is automatically cleaned up when the application process exits. Imagine how happy your Security team will be when they learn that secrets will never live on any developer's machines again!&lt;/p&gt;

&lt;p&gt;To mount an .env file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Node.js&lt;/span&gt;
doppler run &lt;span class="nt"&gt;--mount&lt;/span&gt; .env &lt;span class="nt"&gt;--&lt;/span&gt; npm start

&lt;span class="c"&gt;# PHP&lt;/span&gt;
doppler run &lt;span class="nt"&gt;--mount&lt;/span&gt; .env &lt;span class="nt"&gt;--&lt;/span&gt; php artisan serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To mount a JSON file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler run &lt;span class="nt"&gt;--mount&lt;/span&gt; env.json &lt;span class="nt"&gt;--&lt;/span&gt; npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can specify the format using &lt;code&gt;--mount-format&lt;/code&gt; if the file extension doesn't map to a known format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler run &lt;span class="nt"&gt;--mount&lt;/span&gt; app.config &lt;span class="nt"&gt;--mount-format&lt;/span&gt; json &lt;span class="nt"&gt;--&lt;/span&gt; npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or you can use a custom template, e.g. configure Firebase functions emulator using a &lt;code&gt;.runtimeconfig.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Create the template&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{ "doppler": {{tojson .}} }'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .runtimeconfig.json.tmpl

&lt;span class="c"&gt;# 2. Mount the .runtimeconfig.json and run the emulator&lt;/span&gt;
doppler run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--mount&lt;/span&gt; .runtimeconfig.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--mount-template&lt;/span&gt; .runtimeconfig.json.tmpl &lt;span class="nt"&gt;--&lt;/span&gt; firebase emulators:start &lt;span class="nt"&gt;--only&lt;/span&gt; functions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can even make things more secure by restricting the number of read requests using the --mount-max-reads option, e.g. caching PHP configuration which only requires the .env file to be read once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler run &lt;span class="nt"&gt;--mount&lt;/span&gt; .env &lt;span class="nt"&gt;--mount-max-reads&lt;/span&gt; 1 &lt;span class="nt"&gt;--command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"php artisan config:cache &amp;amp;&amp;amp; php artisan serve"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're wondering what happens to the mounted file if the Doppler process is force killed, its file contents will appear to vanish instantly. The mounted file isn't a regular file at all, but a &lt;a href="https://en.wikipedia.org/wiki/Named_pipe#In_Unix"&gt;Unix named-pipe&lt;/a&gt;. If you've ever heard the phrase "&lt;a href="https://en.wikipedia.org/wiki/Everything_is_a_file"&gt;&lt;em&gt;everything is a file in Unix&lt;/em&gt;&lt;/a&gt;", you now have a better understanding of what that means.&lt;/p&gt;

&lt;p&gt;Named pipes are designed for inter-process communication while still using the file system as the access point. In this case, it's a client-server model, where your application is effectively sending a read request to the Doppler CLI. If the Doppler CLI is force killed, the .env file (named pipe) will still exist, but because no process is attached to it, requests to read the file will simply hang. Just delete the file from your terminal, and you're good to go.&lt;/p&gt;

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

&lt;p&gt;Thanks to SecretOps, we now have the workflows required to prevent secrets from ever living on a developer's machine again. All you need is dynamic secrets injection, ephemeral secrets files, and a single source of truth to pull it all together.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dashboard.doppler.com/register?utm_source=dev&amp;amp;utm_medium=blog&amp;amp;utm_content=how-to-prevent-secrets-from-ending-up-on-developers-machines-40cb"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VldNMgYL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lcu1dmvld1wzmz3b0wl0.png" alt="Create your free Doppler account" width="880" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>programming</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Manually Updating .env Files Isn't DevOps</title>
      <dc:creator>Ryan Blunden</dc:creator>
      <pubDate>Tue, 07 Jun 2022 05:28:43 +0000</pubDate>
      <link>https://forem.com/doppler/manually-updating-env-files-isnt-devops-33i7</link>
      <guid>https://forem.com/doppler/manually-updating-env-files-isnt-devops-33i7</guid>
      <description>&lt;p&gt;Managing .env files and keeping them in sync across environments is painful when done manually.&lt;/p&gt;

&lt;p&gt;Doesn't it seem odd that Slack is still a common way for .env files to be synced between team members? Shouldn't we be concerned that syntax errors from .env file edits are so prevalent that dotenv linting tools are needed?&lt;/p&gt;

&lt;p&gt;It's time to bring the DevOps principles of automation and ephemeral resources to managing environment variables and .env files. This post is a compilation of Doppler's best tips and tricks to do just that as is broken into three sections:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Centralized Environment Variable Management&lt;/li&gt;
&lt;li&gt;Dynamically Injected Environment Variables&lt;/li&gt;
&lt;li&gt;Dynamically Created Ephemeral .env Files&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Although the examples are Doppler centric, the goal is to give you fresh ideas for improving app config and secrets automation in your workplace.  We've got lots of stuff to cover, so let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Centralized Environment Variable Management
&lt;/h2&gt;

&lt;p&gt;It's simply not possible to automate the syncing of environment variables across teams, hosting platforms, and development environments without a centralized source of truth at the organization level.&lt;/p&gt;

&lt;p&gt;Modern platforms such as Heroku and Vercel provide built-in environment variable storage, but unless all of your applications are hosted on a single platform, they can only function as a source of truth for individual applications. And if you're using cloud-based virtual machines such as those from DigitalOcean or AWS EC2, you're on your own to figure out environment variable storage and access.&lt;/p&gt;

&lt;p&gt;With the exception of Vercel's development scoped environment variables, first-class local development support is missing from every modern platform and cloud provider, explaining why so many teams still rely on .env files, even if not used in production. We know how crucial it is for local environments to closely mirror production, but it seems we're willing to make tradeoffs when it comes to environment variables.&lt;/p&gt;

&lt;p&gt;In the past, secrets managers such as &lt;a href="https://blog.doppler.com/doppler-vs-hashicorp-vault"&gt;HashiCorp Vault&lt;/a&gt; were seen as the solution. But replacing the simplicity of environment variables with complex SDKs often resulted in &lt;a href="https://blog.doppler.com/siloed-secrets-and-security-theatre-why-we-need-secretops"&gt;siloed secrets and teams going rogue&lt;/a&gt; by managing environment variables their own way. Cloud secrets managers also didn't improve the local development story.&lt;/p&gt;

&lt;p&gt;Essentially, we need a new way of managing environment variables that reflects the needs of modern application development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we need SecretOps
&lt;/h2&gt;

&lt;p&gt;SecretOps is designed for multi-cloud deployments and combines the strengths of traditional solutions while addressing their weaknesses.As a starting point, a SecretOps platform must:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Centralize the storage and management of secrets&lt;/li&gt;
&lt;li&gt;Provide flexible and secure environment variable injection for every platform and cloud&lt;/li&gt;
&lt;li&gt;Provide a first-class local development experience&lt;/li&gt;
&lt;li&gt;Increase Developer productivity through secrets automation workflows&lt;/li&gt;
&lt;li&gt;Decrease the complexity of secrets management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using &lt;a href="https://www.doppler.com"&gt;Doppler&lt;/a&gt; as an example, we're tackling these requirements by providing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A fully-hosted solution with collaborative secrets management workflows and fine-grained access controls, all from a single centralized dashboard.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://docs.doppler.com/docs/install-cli"&gt;CLI&lt;/a&gt; for injecting environment variables and ephemeral .env files in any environment, from local development to CI/CD, Docker, Kubernetes, Virtual Machines etc.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.doppler.com/docs/integrations"&gt;Integrations&lt;/a&gt; that sync secrets to platforms such as Vercel, Netlify, Heroku, and GitHub Secrets, so applications don't have to integrate with Doppler directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Doppler's operating model is that managing secrets should be centralized, but fetching and syncing secrets should be tailored to every customer's needs. For example, many of our customers enjoy the Doppler dashboard's superior features and developer experience but sync secrets to Azure Key Vault so production secrets access remains as is. &lt;/p&gt;

&lt;p&gt;Our vision for SecretOps is constantly evolving. Our goal is to share, inspire, and help move our industry forward with new ideas that take secrets automation to the next level.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Dynamically Injected Environment Variables
&lt;/h2&gt;

&lt;p&gt;Using Doppler to illustrate, let's look at several methods for environment variable injection.&lt;/p&gt;

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

&lt;p&gt;Having a platform or infrastructure tool to inject environment variables into your application is the best solution, as you can then say goodbye to .env files altogether.&lt;/p&gt;

&lt;p&gt;So while that removes the need for .env files in select platforms, additional tooling is needed for virtual machines, local development, and Kubernetes, just to name a few.&lt;/p&gt;

&lt;h3&gt;
  
  
  CLI Application Runner
&lt;/h3&gt;

&lt;p&gt;This method uses a CLI to run your application, injecting environment variables directly into the application process.&lt;/p&gt;

&lt;p&gt;Here is how the &lt;a href="https://docs.doppler.com/docs/install-cli"&gt;Doppler CLI&lt;/a&gt; can be used to inject environment variables into a Node.js application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler run &lt;span class="nt"&gt;--&lt;/span&gt; npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also use a script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler run &lt;span class="nt"&gt;--&lt;/span&gt; ./launch-app.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a long-running background process in a virtual machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;nohup &lt;/span&gt;doppler run &lt;span class="nt"&gt;--&lt;/span&gt; npm start &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use the Doppler CLI inside a Docker container:&lt;br&gt;
&lt;/p&gt;

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

# Install Doppler CLI
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-Ls&lt;/span&gt; &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.2 &lt;span class="nt"&gt;--proto&lt;/span&gt; &lt;span class="s2"&gt;"=https"&lt;/span&gt; &lt;span class="nt"&gt;--retry&lt;/span&gt; 3 https://cli.doppler.com/install.sh | sh

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["doppler", "run", "--", "npm", "start"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A CLI application runner with environment variable injection should have the following properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Be open source&lt;/li&gt;
&lt;li&gt;Support every major operating system&lt;/li&gt;
&lt;li&gt;Installable via package managers&lt;/li&gt;
&lt;li&gt;Correctly passes signals to the application (e.g SIGINT) so your application can terminate gracefully&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;doppler run&lt;/code&gt; command is just one way of accessing secrets, and you can find more examples in our &lt;a href="https://docs.doppler.com/docs/accessing-secrets"&gt;CLI Secrets Access Guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Docker Container Environment Variables
&lt;/h3&gt;

&lt;p&gt;This method injects environment variables into a Docker container at runtime, removing the temptation of embedding an .env file in the Docker image (yes, it happens) and avoiding the host creating and mounting the .env file inside the container.&lt;/p&gt;

&lt;p&gt;The Doppler CLI pipes secrets in .env file format to the Docker CLI where it reads the output as a file using &lt;a href="https://tldp.org/LDP/abs/html/process-sub.html"&gt;process substitution&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env-file&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;doppler secrets download &lt;span class="nt"&gt;--no-file&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; docker&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  my-awesome-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we get the benefits of .env file configuration but without an .env file ever touching the disk.&lt;/p&gt;

&lt;p&gt;You can see other use cases in our &lt;a href="https://github.com/DopplerUniversity/docker-examples"&gt;docker-examples GitHub repository&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Docker Compose differs from Docker as it accesses environment variables from the host when &lt;code&gt;docker compose up&lt;/code&gt; is run.&lt;/p&gt;

&lt;p&gt;Docker Compose sensibly requires you to define which environment variables to pass through to each service as variables such as &lt;code&gt;$PATH&lt;/code&gt; are host-specific:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.9'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
        &lt;span class="c1"&gt;# Host environment variables passed through to container&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AUTH_TOKEN&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_CONNECTION_URL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because of how Docker Compose access environment variables, we can use the Doppler CLI as an application runner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler run &lt;span class="nt"&gt;--&lt;/span&gt; docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other Docker Compose use cases can be found in our &lt;a href="https://github.com/DopplerUniversity/docker-examples/tree/main/docker-compose-pem-certificates"&gt;docker-examples GitHub repository&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Kubernetes provides excellent support for injecting environment variables into containers using Key-Value pairs stored in a &lt;a href="https://kubernetes.io/docs/concepts/configuration/secret/"&gt;Kubernetes secret&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Doppler provides two options for syncing secrets to Kubernetes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Doppler CLI Created Secrets&lt;/li&gt;
&lt;li&gt;Doppler Kubernetes Operator Managed Secrets&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Doppler CLI Created Secrets
&lt;/h3&gt;

&lt;p&gt;The first step is to create a generic Kubernetes secret (the first argument being the secret's name) where just like Docker, secrets in .env file format are piped to &lt;code&gt;kubectl&lt;/code&gt;  where it reads the output as a file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create secret generic my-app-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-env-file&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;doppler secrets download &lt;span class="nt"&gt;--no-file&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; docker&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Doppler Kubernetes Operator Managed Secrets
&lt;/h3&gt;

&lt;p&gt;Our &lt;a href="https://docs.doppler.com/docs/kubernetes-operator"&gt;Kubernetes Operator&lt;/a&gt; is designed to scale and fully automate secrets syncing from Doppler to Kubernetes. Once installed and configured, it instantly creates and updates Kubernetes secrets as they change in Doppler with support for automatic deployment reloads if the secrets they're consuming have changed.&lt;/p&gt;

&lt;p&gt;As it's a more advanced solution that requires Kubernetes cluster administration experience, we won't be covering it here but check out our &lt;a href="https://docs.doppler.com/docs/kubernetes-operator"&gt;Kubernetes Operator documentation&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kubernetes Deployments and Environment Variables
&lt;/h3&gt;

&lt;p&gt;Injecting environment variables into a Deployment from the Key-Value pairs in a Kubernetes secret is done using the &lt;code&gt;envFrom&lt;/code&gt; property of a container spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;awesome-app&lt;/span&gt;
          &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app-secret&lt;/span&gt; &lt;span class="c1"&gt;# Kubernetes secret name&lt;/span&gt;
&lt;span class="s"&gt;…&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hopefully, you've learned some new tricks for environment variable injection! Now let's move on to.env files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamically Created Ephemeral .env Files
&lt;/h2&gt;

&lt;p&gt;Environment variable injection is always preferred, but sometimes, an .env file is the only workable solution.&lt;/p&gt;

&lt;p&gt;Protective measures such as locking down file ownership, file permissions, and heavily restricting shell access should go without saying. But the risk of .env files existing on the file system indefinitely has always been a concern and why we've been hesitant to recommend .env file usage in the past.&lt;/p&gt;

&lt;p&gt;But thanks to the Doppler CLI, we can now mount ephemeral .env files that are automatically cleaned up when the application exits. Imagine not having to worry about anyone in your company accidentally committing an .env file again! &lt;/p&gt;

&lt;p&gt;One of the most popular use cases is for PHP developers building Laravel applications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler run &lt;span class="nt"&gt;--mount&lt;/span&gt; .env &lt;span class="nt"&gt;--&lt;/span&gt; php artisan serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file extension is used to automatically set the format (JSON format is also supported):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler run &lt;span class="nt"&gt;--mount&lt;/span&gt; secrets.json &lt;span class="nt"&gt;--&lt;/span&gt; npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can set the format if the file extension doesn't map to a known type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler run &lt;span class="nt"&gt;--mount&lt;/span&gt; app.config &lt;span class="nt"&gt;--mount-format&lt;/span&gt; json &lt;span class="nt"&gt;--&lt;/span&gt; npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To increase security, you can also restrict the number of reads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler run &lt;span class="nt"&gt;--mount&lt;/span&gt; .env &lt;span class="nt"&gt;--mount-max-reads&lt;/span&gt; 1 &lt;span class="nt"&gt;--command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"php artisan config:cache &amp;amp;&amp;amp; php artisan serve"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're wondering what happens to the mounted file if the Doppler process is force killed, its file contents will appear to vanish instantly. This is because the mounted file isn't a regular file at all, but a &lt;a href="https://en.wikipedia.org/wiki/Named_pipe#In_Unix"&gt;Unix named-pipe&lt;/a&gt;. If you've ever heard the phrase “&lt;a href="https://en.wikipedia.org/wiki/Everything_is_a_file"&gt;&lt;em&gt;everything is a file in Unix&lt;/em&gt;&lt;/a&gt;”, you now have a better understanding of what that means.&lt;/p&gt;

&lt;p&gt;Named pipes are designed for inter-process communication while still using the file system as the access point. In this case, it's a client-server model, where your application is effectively sending a read request to the Doppler CLI. In the event the Doppler CLI is force killed, the .env file (named pipe) will still exist, but because no process is attached to it, requests to read the file will simply hang.&lt;/p&gt;

&lt;p&gt;It's also named pipes that enable us to restrict the number of reads using the &lt;code&gt;--mount-max-reads&lt;/code&gt; option as once the limit is exceeded, the CLI simply removes the named pipe from the file system.&lt;/p&gt;

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

&lt;p&gt;I hope you take away some new automation ideas to bring back to your team so you can spend less time updating .env files and more time shipping software.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dashboard.doppler.com/register?utm_source=dev&amp;amp;utm_medium=blog&amp;amp;utm_content=manually-updating-env-files-isnt-devops-33i7"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VldNMgYL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lcu1dmvld1wzmz3b0wl0.png" alt="Create your free Doppler account" width="880" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>programming</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Crash course in Web3 Application Development with Python</title>
      <dc:creator>Tyler Langlois</dc:creator>
      <pubDate>Tue, 31 May 2022 13:00:00 +0000</pubDate>
      <link>https://forem.com/doppler/crash-course-in-web3-application-development-with-python-1a1e</link>
      <guid>https://forem.com/doppler/crash-course-in-web3-application-development-with-python-1a1e</guid>
      <description>&lt;p&gt;In the world of cryptocurrency, private keys are king. Mathematical mechanics treat correctly signed transactions as valid regardless of intent, which makes account keys and passphrases a particularly lucrative target for malicious actors. While &lt;a href="https://blog.doppler.com/siloed-secrets-and-security-theatre-why-we-need-secretops"&gt;end-user best practices for securing keys&lt;/a&gt; tend to be plentiful, the development side of blockchain work takes place amid a sea of API tokens, key files, and service credentials that can &lt;a href="https://blog.doppler.com/secrets-sprawl-is-hurting-your-productivity-heres-how-to-fix-it"&gt;sprawl out of control&lt;/a&gt; if not managed correctly.&lt;/p&gt;

&lt;p&gt;Ensuring that these sensitive values remain secure is critical and &lt;a href="https://blog.doppler.com/the-triumph-and-tragedy-of-env-files"&gt;outdated solutions such as &lt;code&gt;.env&lt;/code&gt;&lt;/a&gt; files are no longer sufficient.&lt;/p&gt;

&lt;p&gt;This tutorial will start from the basics of interacting with blockchain APIs from joining as a peer upwards. There are numerous third-party services and APIs that sit atop various blockchains, but establishing secure management practices for primitives such as private keys is fundamental.&lt;/p&gt;

&lt;p&gt;No prior experience interacting with blockchain or web3 services is assumed, and we'll use a &lt;a href="https://en.wikipedia.org/wiki/Testnet"&gt;testnet&lt;/a&gt; in order to limit the potential exposure of sensitive information. &lt;a href="https://www.doppler.com/"&gt;Doppler&lt;/a&gt; will be used to store and retrieve credentials associated with private key material, which is a secure and convenient way to manage secrets without the risks of storing them on disk in unencrypted text files.&lt;/p&gt;

&lt;p&gt;Before downloading any programs or generating account keys, there are a few concepts to establish heading into the unique paradigms that accompany working with web3 networks.&lt;/p&gt;

&lt;p&gt;While plenty of unique blockchains and products abound in the web3 space, this guide will focus on the &lt;a href="https://en.wikipedia.org/wiki/Ethereum"&gt;Ethereum&lt;/a&gt; blockchain, which is one of the more commonly used networks. Like most cryptocurrency blockchains, Ethereum operates in a decentralized way with a vast network of computers that can be interacted with as a peer. However, any sort of operation that requires some proof of ownership, e.g. signing a transaction, does require the authoritative stamp of a private key.&lt;/p&gt;

&lt;p&gt;This is the core of how the network operates, which makes the method of communication and management of privileged information a chief concern.&lt;/p&gt;

&lt;p&gt;This tutorial will follow a basic outline to work directly with the Ethereum blockchain and its API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connecting to the testnet&lt;/li&gt;
&lt;li&gt;Generating and securing public keys&lt;/li&gt;
&lt;li&gt;Receiving tokens&lt;/li&gt;
&lt;li&gt;Sending tokens securely&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's begin!&lt;/p&gt;

&lt;h2&gt;
  
  
  A Chain Primer
&lt;/h2&gt;

&lt;p&gt;When choosing how to connect to the Ethereum blockchain, there are two general approaches, each with distinct advantages and disadvantages.&lt;/p&gt;

&lt;p&gt;The first is by connecting to a third-party API that serves as a frontend to low-level blockchain network protocols. Services like &lt;a href="https://infura.io/"&gt;Infura&lt;/a&gt; offer a web-based JSON RPC API, which is widely compatible with a range of libraries and languages and allows developers to interact with the blockchain without downloading the entire ledger, which can take up significant compute resources and disk space (around 500GB at the time of this writing).&lt;/p&gt;

&lt;p&gt;Using a third-party API means that your code can run from a variety of environments without being tethered to a running node and the accompanying resource requirements that are necessary. The downside to this approach is reliance upon a third party when one of the core advantages of blockchain technology is &lt;em&gt;decentralisation&lt;/em&gt;. Routing communication with the aid of an intermediary party is convenient, but means that clients must trust the provider and are subject to the inherent properties of using a hosted API instead of communicating directly via peer protocols (such as availability and uptime). These tradeoffs are often the right choice, but are tradeoffs nonetheless.&lt;/p&gt;

&lt;p&gt;The second approach is to run a native node to communicate to the chosen blockchain directly. As previously mentioned, there are significant computational costs involved, but this means that the client speaks directly to the chosen peer network which brings with it benefits like smaller risk exposure, high availability of the decentralised network, and the ability to self-validate the ledger.&lt;/p&gt;

&lt;p&gt;There are additional considerations to take into account when operating a dedicated node, such as choosing whether to download the &lt;em&gt;entire&lt;/em&gt; ledger during the initial loading process or instead electing a more lightweight method that incurs a smaller disk usage cost.&lt;/p&gt;

&lt;p&gt;This guide will opt for the self-hosted node technique, though in practice, either approach is a reasonable choice.&lt;/p&gt;

&lt;p&gt;Finally, a node operator must select which network to connect to when initialising their connection.&lt;/p&gt;

&lt;p&gt;Normally, users will choose to connect to the live, "production" chain when interacting with a public ledger, but during local development and testing, a testnet is a better choice. In addition to less traffic (resulting in smaller ledgers and transaction costs), acquiring tokens to send and receive is usually free and does not require any initial funds. This is a significant advantage when sending and receiving tokens without risk of making mistakes that could incur significant consequences.&lt;/p&gt;

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

&lt;p&gt;We'll use &lt;a href="https://geth.ethereum.org/"&gt;geth&lt;/a&gt; (Go Ethereum) to connect to the Ethereum network, which is a golang implementation that can function not only as a client but a fully featured node as well.&lt;/p&gt;

&lt;p&gt;First, follow the &lt;a href="https://geth.ethereum.org/docs/install-and-build/installing-geth"&gt;instructions for installing geth&lt;/a&gt; for your operating system. Once installed, the &lt;code&gt;geth&lt;/code&gt; command should be available from the terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;geth version
Geth
Version: 1.10.17-stable
Architecture: amd64
Go Version: go1.16.13
Operating System: linux
GOPATH=
GOROOT=/nix/store/j1x3cy8g2cqcr54rg98gw0mdc28jlyc8-go-1.16.13/share/go

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

&lt;/div&gt;



&lt;p&gt;The command-line flags passed to the geth CLI dictate which network it will connect to. There are &lt;a href="https://ethereum.org/en/developers/docs/networks/#testnets"&gt;a number of testnets within the Ethereum network&lt;/a&gt; to choose from, and this guide will use the Goerli proof-of-authority network as its newer than some of the others, and is not coupled with the computationally-expensive proof-of-work networks.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;--syncmode&lt;/code&gt; flag controls the method that &lt;code&gt;geth&lt;/code&gt; will use when downloading the historical ledger of the blockchain. On one end of the extreme, the &lt;code&gt;full&lt;/code&gt; method will download the entirety of the blockchain ledger which includes all the relevant transactional data. On the other, the &lt;code&gt;light&lt;/code&gt; method will only fetch recent block headers and retrieve other information on-demand. When communicating with the production Ethereum network, the &lt;code&gt;full&lt;/code&gt; method offers the most comprehensive assurance that the local copy of blockchain data is valid and legitimate. However, in this exploratory case on a testnet, the &lt;code&gt;light&lt;/code&gt; method is sufficient.&lt;/p&gt;

&lt;p&gt;Begin the &lt;code&gt;geth&lt;/code&gt; process in a background terminal on your machine. The initial syncing process will take time, but using the &lt;code&gt;light&lt;/code&gt; method on the &lt;code&gt;goerli&lt;/code&gt; testnet will not use excessive space (at the time of this writing, the entirety of the chain requires about 500MB of disk space). The following command will store the Goerli chain data in the &lt;code&gt;~/.goerli&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;geth --syncmode light --datadir ~/.goerli --goerli
INFO [04-05|15:54:49.916] Starting Geth on Goerli testnet...
...

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

&lt;/div&gt;



&lt;p&gt;After some time, the daemon will stop importing historical blocks and the latest blocks will be available.&lt;/p&gt;

&lt;p&gt;The logging output from &lt;code&gt;geth&lt;/code&gt; will include the &lt;code&gt;age&lt;/code&gt; of a block when performing initial synchronisation. When you see blocks being imported with output such as &lt;code&gt;age=4m04w1d&lt;/code&gt;, this indicates old blocks being retrieved to construct the local chain. Once older blocks stop appearing, this indicates that the initial synchronisation is complete.&lt;/p&gt;

&lt;p&gt;You can continue with the rest of this guide while the blockchain synchronises; full synchronisation will only be necessary once we reach the portion that covers transactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accounts and Keys
&lt;/h2&gt;

&lt;p&gt;At the beginning of this guide, we spoke at length about the importance of keys when interacting with the blockchain.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Public-key_cryptography"&gt;Public-key cryptography&lt;/a&gt; forms the backbone of the cryptographic methods for forming and authenticating transactions on public ledgers. Private keys prove ownership of public addresses and sign transactions to send tokens to other public addresses.&lt;/p&gt;

&lt;p&gt;While high-level, user-facing cryptocurrency applications may leverage account information for wallets in the form of traditional username/password combinations, the core values that comprise an "account" on a blockchain like Ethereum is a cryptographic key - tokens belong to an addressed coupled to a private key. The geth CLI includes utilities to generate keys, which it calls an &lt;strong&gt;account&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To begin, create a new account. Remember that this "account" is really a locally encrypted file and not an account that can be "logged into" from another machine - if you are using an account tied to a production blockchain, it's critical to ensure that this account is secured by a strong passphrase backed up regularly.&lt;/p&gt;

&lt;p&gt;In our case, the local account and keypair will be associated with a testnet, but we'll still use best practices for managing the passphrase by storing it in encrypted secrets store (Doppler) and treat the account as sensitive. Because the running &lt;code&gt;geth&lt;/code&gt; process is active, we'll connect to the running daemon via &lt;code&gt;attach&lt;/code&gt; instead of using the &lt;code&gt;geth account&lt;/code&gt; subcommand.&lt;/p&gt;

&lt;p&gt;Enter the &lt;code&gt;geth&lt;/code&gt; console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;geth --datadir ~/.goerli attach

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

&lt;/div&gt;



&lt;p&gt;You'll be greeted by the command prompt for &lt;code&gt;geth&lt;/code&gt; javascript commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Welcome to the Geth JavaScript console!

instance: Geth/v1.10.17-stable/linux-amd64/go1.16.13
at block: 6664849 (Tue Apr 05 2022 17:34:43 GMT-0600 (MDT))
datadir: /home/yourname/.goerli
modules: admin:1.0 clique:1.0 debug:1.0 eth:1.0 les:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 vflux:1.0 web3:1.0

To exit, press ctrl-d or type exit
&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;To create a new account, run the following command where you'll be asked to enter a passphrase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;personal.newAccount()

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

&lt;/div&gt;



&lt;p&gt;After following the prompts, &lt;code&gt;geth&lt;/code&gt; will create a keypair within the &lt;code&gt;~/.goerli/keystore/&lt;/code&gt; directory. You can view the public address of this account at any time by using the following command from the &lt;code&gt;geth&lt;/code&gt; console:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;At this point, your local machine will have a light copy of the Ethereum Goerli testnet blockchain and a keypair ready to send and receive tokens. Let's begin working with transactions!&lt;/p&gt;

&lt;h2&gt;
  
  
  Interacting with the Blockchain
&lt;/h2&gt;

&lt;p&gt;A simple task to confirm the functionality of this local testnet setup is to receive currency. Unlike currency on the live, production blockchain, currency on a testnet is often given away freely to permit developers to test and iterate on projects and products.&lt;/p&gt;

&lt;p&gt;Providers for free currency - often called "faucets" - may sometimes be unavailable or lacking sufficient funds to distribute tokens freely. At the time of this writing, the faucet mentioned here is functional and has sufficient funds to distribute them to anyone who requests them, but if you encounter problems requesting funds at a later date than when this tutorial was written, you may also seek out alternate sources (remember that they must be operating on the Goerli Ethernet testnet).&lt;/p&gt;

&lt;p&gt;Well be using the &lt;a href="https://goerlifaucet.com/"&gt;Goerli Faucet&lt;/a&gt; to acquire initial funds to begin experimenting with the blockchain.&lt;/p&gt;

&lt;p&gt;From the &lt;code&gt;geth&lt;/code&gt; console, find your public address using &lt;code&gt;listAccounts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;personal.listAccounts
["0x..."]

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

&lt;/div&gt;



&lt;p&gt;Copy the address, navigate to the &lt;a href="https://goerlifaucet.com/"&gt;Goerli Faucet&lt;/a&gt;, paste your address into the Wallet Address field, then click &lt;strong&gt;Send Me ETH&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---Mwlk3Ox--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1653001171755/c5pk5BGRt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---Mwlk3Ox--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1653001171755/c5pk5BGRt.png" alt="Goerli Testnet Faucet" width="880" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Blocks are minted frequently on this chain, so it's time to check your balance! We'll use the &lt;a href="https://web3py.readthedocs.io/en/stable/index.html"&gt;Web3&lt;/a&gt; Python library to interact with the local node.&lt;/p&gt;

&lt;p&gt;This guide assumes that you have Python 3 installed on your local machine. Weve provided a &lt;a href="https://github.com/DopplerUniversity/python-web3"&gt;repository with sample code&lt;/a&gt; as a quick start to getting up to speed with how to use the Web3 library.&lt;/p&gt;

&lt;p&gt;Clone the GitHub repository then enter the repository directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/DopplerUniversity/python-web3
cd python-web3

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

&lt;/div&gt;



&lt;p&gt;Create a Python virtual environment and install the dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

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

&lt;/div&gt;



&lt;p&gt;Feel free to open &lt;code&gt;web3-app.py&lt;/code&gt; and take a look! The &lt;a href="https://pypi.org/project/web3/"&gt;Python web3&lt;/a&gt; library makes accessing blockchain information fairly straightforward, so this example code should be a good place to start if you want to experiment later.&lt;/p&gt;

&lt;p&gt;Let's verify the script is working by fetching the timestamp of the latest block on the chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python web3-app.py latest-block

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

&lt;/div&gt;



&lt;p&gt;If you see a timestamp that is within 30 seconds or so, congratulations! You've successfully interacted with your local instance of the testnet blockchain. The next task is to check the balance of the address that your faucet tokens were sent to.&lt;/p&gt;

&lt;p&gt;First, confirm that Python is able to see the local account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python web3-app.py accounts

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

&lt;/div&gt;



&lt;p&gt;Your account number should match the address that you previously found within the &lt;code&gt;geth&lt;/code&gt; console using &lt;code&gt;personal.listAccounts&lt;/code&gt;. Finally, check the account balance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python web3-app.py balance

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

&lt;/div&gt;



&lt;p&gt;The account balance for the address entered in the Ethereum faucet site should be &lt;code&gt;0.05&lt;/code&gt; ETH. You've checked your account balance from the python &lt;code&gt;web3&lt;/code&gt; library. Well done!&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending ETH
&lt;/h2&gt;

&lt;p&gt;Until now we've only interacted with the local blockchain with APIs that do not require private account keys. In order to finish this experiment and successfully sign a transaction with your account, we'll practice by sending an amount of ETH back to the Goerli faucet. Doing so will help demonstrate the process as well as replenish funds to future developers on the blockchain!&lt;/p&gt;

&lt;p&gt;To send ETH, well use the &lt;code&gt;send_transaction&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;web3.geth.personal.send_transaction(self, transaction, passphrase)

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

&lt;/div&gt;



&lt;p&gt;Remember when we created our local Geth account and provided the passphrase?&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;passphrase&lt;/em&gt; is required here to unlock the private key and sign the transaction in order to validate it as a legitimate transfer of funds out of our account. However, entering a passphrase into a REPL or a shell is a security concern: most REPL and shell histories (like bash or the python interpreter) retain commands that are entered, so it's wise to avoid entering a passphrase manually in plaintext.&lt;/p&gt;

&lt;p&gt;What other options exist? One approach is to read secret values - whether it be a passphrase, API key, or otherwise from a file on disk such as &lt;code&gt;.env&lt;/code&gt; or JS file but this is a dangerous option as well explain.&lt;/p&gt;

&lt;p&gt;Storing secrets in plaintext files is a huge security nightmare are as they can be read by other user processes, and risk being committed during work with tools like git if a developer makes a mistake such as &lt;a href="https://twitter.com/nateliason/status/1392086702794149894"&gt;Nat Eliason did when he lost $30,000&lt;/a&gt; by accidentally pushing a file containing his passphrase to GitHub.&lt;/p&gt;

&lt;p&gt;Reading the value from an environment variable is a good idea, but that begs the question of how the environment variable containing the passphrase will be securely populated.&lt;/p&gt;

&lt;p&gt;This is where a SecretOps platform such as &lt;a href="https://www.doppler.com/"&gt;Doppler&lt;/a&gt; can help by using the Doppler CLI to inject secrets as environment variables into an application process. This allows an environment variable to be populated with the passphrase securely without the risks of unencrypted local file storage.&lt;/p&gt;

&lt;p&gt;To begin, first &lt;a href="https://dashboard.doppler.com/register"&gt;sign up&lt;/a&gt; for a Doppler account. The sign up process will walk through a few helpful tips to get started, and once your account has been configured, proceed to &lt;a href="https://docs.doppler.com/docs/install-cli"&gt;install the Doppler CLI&lt;/a&gt;, then authenticate your machine by running:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Create a new project called &lt;strong&gt;python-web3&lt;/strong&gt; which will hold the secrets for this project and keep things organised as well as within their own environment scope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;doppler projects create python-web3
doppler setup --project python-web3 --config dev

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

&lt;/div&gt;



&lt;p&gt;Ensure that you run &lt;code&gt;doppler setup&lt;/code&gt; in the python-web3 directory as the Doppler CLI scopes secrets access to specific directories. Eventually you should have a setup similar to the one below: with your project directory coupled to the &lt;code&gt;python-web3&lt;/code&gt; project in Doppler.&lt;/p&gt;

&lt;p&gt;Run the following command to check you can access secrets from the python-web3 directory:&lt;br&gt;
&lt;/p&gt;

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

 ID NAME DESCRIPTION CREATED AT               

 python-web3 python-web3 2022-05-24T23:40:34.955Z 

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

&lt;/div&gt;



&lt;p&gt;The next step is to store the keychain passphrase in Doppler where it can be retrieved by the &lt;code&gt;doppler&lt;/code&gt; CLI.&lt;/p&gt;

&lt;p&gt;Open the &lt;a href="https://dashboard.doppler.com/workplace/projects/python-web3/configs/dev"&gt;python-web3 Doppler Project&lt;/a&gt; and create a &lt;code&gt;ETH_PASSPHRASE&lt;/code&gt; with your passphrase value, then click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IT_KQduu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1653409515944/nw2Wac9uL.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IT_KQduu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1653409515944/nw2Wac9uL.png" alt="Doppler configuration dashboard" width="880" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Heading back to the terminal, can verify Doppler saved your passphrase successfully by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;doppler secrets get ETH_PASSPHRASE

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

&lt;/div&gt;



&lt;p&gt;With the passphrase managed by Doppler, we can now run the python script with secrets populated by the &lt;code&gt;doppler run&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;You may inspect the accompanying python code from the cloned repository, but the key lines are those that &lt;a href="https://github.com/DopplerUniversity/python-web3/blob/main/web3-app.py#L30"&gt;retrieve your passphrase&lt;/a&gt; from the &lt;code&gt;ETH_PASSPHRASE&lt;/code&gt; environment variable.&lt;/p&gt;

&lt;p&gt;When you invoke the &lt;code&gt;doppler run&lt;/code&gt; command, Doppler will set the &lt;code&gt;ETH_PASSPHRASE&lt;/code&gt; environment variable for python to use and sign the transaction with the wallet information from the unlocked key. Although the noted address is that of the faucet you originally received your balance from, you may choose an arbitrary amount to send with your command (as long as you have sufficient funds!).&lt;/p&gt;

&lt;p&gt;You can find a random account to send funds to from the &lt;a href="https://goerli.etherscan.io/accounts"&gt;Goerli top accounts page&lt;/a&gt;. Copy one of the to addresses (starting with 0x) and replace &lt;code&gt;{ADDRESS}&lt;/code&gt; in the command below to begin the funds transfer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;doppler run -- python web3-app.py send {ADDRESS} 0.01

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

&lt;/div&gt;



&lt;p&gt;The transaction once complete will return a unique transaction hash like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HexBytes('0x0d8c2b6924a88cda73c73275051245db7a7e698f419a9d27b8b185dda16a8436')

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

&lt;/div&gt;



&lt;p&gt;Your transaction has been submitted to the blockchain! At this point the running &lt;code&gt;geth&lt;/code&gt; light node will broadcast this transaction and the transfer of funds will propagate through the network. You may use a few approaches to view the transaction details.&lt;/p&gt;

&lt;p&gt;The provided python script supports a command to introspect an arbitrary transaction ID for example, the following command will display the transaction details of the aforementioned transaction ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python web3-app.py get-txn 0x0d8c2b6924a88cda73c73275051245db7a7e698f419a9d27b8b185dda16a8436

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

&lt;/div&gt;



&lt;p&gt;In addition to viewing details from a local client, using another method to introspect the public ledger is a good idea in order to confirm that the transaction is visible from any client.&lt;/p&gt;

&lt;p&gt;For example, you can either use a generally-available web-based block explorer to view transactions associated with a specific address (replace the given hash with yours that is under the variable in your python repl as &lt;code&gt;my_account&lt;/code&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://goerli.etherscan.io/address/0x858Eb06Bd4dc5BE36Dc5025483a316E1630b35e9"&gt;https://goerli.etherscan.io/address/0x858Eb06Bd4dc5BE36Dc5025483a316E1630b35e9&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or you may alternatively view the transaction directly by entering your transaction hash into a URL like the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://goerli.etherscan.io/tx/0x0d8c2b6924a88cda73c73275051245db7a7e698f419a9d27b8b185dda16a8436"&gt;https://goerli.etherscan.io/tx/0x0d8c2b6924a88cda73c73275051245db7a7e698f419a9d27b8b185dda16a8436&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In either case you should be able to see the transaction data including the amount and associated gas price.&lt;/p&gt;

&lt;p&gt;Congratulations! You just created a transaction on an Ethereum blockchain!&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Considerations
&lt;/h2&gt;

&lt;p&gt;This guide has covered one of the most basic operations when working with a blockchain - signing and broadcasting a transaction - using Doppler to manage your passphrase securely so crypto keys and passphrases arent inadvertently exposed along with any other sensitive account credentials.&lt;/p&gt;

&lt;p&gt;Recall that in this tutorial, we generated a keychain using the &lt;code&gt;geth&lt;/code&gt; CLI so the associated public and private key exist only on the local machine.&lt;/p&gt;

&lt;p&gt;To write code that could potentially run on any host with a keychain stored remotely, you could elect to store key material in Doppler and instantiate a local keychain based upon the retrieval of that information from a similar Doppler secret.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;web3&lt;/code&gt; library provides the &lt;code&gt;web3.geth.personal.import_raw_key&lt;/code&gt; method which accepts a private key and passphrase so that your transaction operations can occur on any machine with a properly setup Doppler environment. The local blockchain can be used for more experiments by virtue of the flexibility of the testnet environment, so you can continue to receive currency from a Goerli testnet faucet and send funds to other addresses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;p&gt;For additional information, these resources provide more documentation and information about the libraries and tools used in this guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://web3py.readthedocs.io/en/stable/index.html"&gt;Web3.py documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ethereum.org/en/developers/docs/transactions/"&gt;Transactions | ethereum.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ethereum.org/en/developers/docs/networks/"&gt;Networks | ethereum.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://goerli.net/"&gt;Goerli Testnet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.doppler.com/docs/getting-started"&gt;Doppler SecretOps Platform | Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>$20m Series A to build the first SecretOps Platform</title>
      <dc:creator>Brian Vallelunga</dc:creator>
      <pubDate>Thu, 28 Apr 2022 01:19:19 +0000</pubDate>
      <link>https://forem.com/doppler/20m-series-a-to-build-the-first-secretops-platform-2882</link>
      <guid>https://forem.com/doppler/20m-series-a-to-build-the-first-secretops-platform-2882</guid>
      <description>&lt;h2&gt;
  
  
  Redefining secrets management through multi-cloud secrets sync and automation at enterprise scale.
&lt;/h2&gt;

&lt;p&gt;We're excited to announce our $20m Series A round led by CRV with participation from existing investors including Google Ventures, Sequoia Capital, and Y Combinator.&lt;/p&gt;

&lt;p&gt;40 tech leaders have also joined as angel investors and advisors including GitHub CEO Thomas Dohmke, Datadog CEO Olivier Pomel, Plaid CEO Jean-Denis Greze, Twilio CEO Evan Cooke, Postman CEO Ankit Sobti, and ​​Okta CEO Frederic Kerrest.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C4q0vVwd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bg0shf6retx68kw4bakt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C4q0vVwd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bg0shf6retx68kw4bakt.jpg" alt="Image description" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By building the first SecretOps Platform, developers and security teams can leave behind the problems caused by .env files, unmanageable secrets sprawl across multiple clouds, and the lack of innovation from traditional secret managers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The ability to securely store, transmit, and audit secrets has never been more critical as one minor error can lead to catastrophic results.&lt;/p&gt;

&lt;p&gt;In a world where putting a single space in the wrong place can literally take down a company’s entire website, Doppler makes it easy to prevent leaks and outages with their developer-focused approach.&lt;/p&gt;

&lt;p&gt;Murat Bicer, General Partner, CRV&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A SecretOps Platform is the single source of truth for developers and their teams to manage, store, and automatically sync secrets to every major hosting platform, secrets manager, and infrastructure management tooling such as Kubernetes and Terraform.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The reality is that the legacy way of storing secrets in siloed secrets managers and .env files doesn’t make sense anymore.&lt;/p&gt;

&lt;p&gt;With Doppler, we can put those days behind us. Just like when GitHub first pioneered the notion of a pull request,—once you see the benefits, there's no turning back.&lt;/p&gt;

&lt;p&gt;Brian Vallelunga, Doppler CEO&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fuelling Doppler's growth are customers such as Puma, Hopin, ezCater, Toast, Gather, My Muscle Chef, OnDeck, and many others. More than 15,000 teams trust Doppler to manage their secrets with over 1.5 billion secrets synced every month.&lt;/p&gt;

&lt;p&gt;Traditional secrets managers were never built to support a multi-cloud deployment strategy or provide developers with the management features they need as part of the software development lifecycle and deployment process.&lt;/p&gt;

&lt;p&gt;But we don’t just need slightly better secrets managers. We need a SecretOps Platform. A platform that satisfies the requirements of both Developers and Security Engineers. Development teams and DevSecOps.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Doppler combines all of the key elements DevOps and Security teams need to control who can see and modify secrets at scale as well as an audit trail, versioning, enterprise grade encryption, secrets rotation and dynamic secrets.&lt;/p&gt;

&lt;p&gt;Brian Vallelunga, Doppler CEO&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Doppler also seamlessly integrates and syncs secrets to a growing list of infrastructure tools such as &lt;a href="https://docs.doppler.com/docs/kubernetes-operator"&gt;Kubernetes&lt;/a&gt; and &lt;a href="https://docs.doppler.com/docs/terraform"&gt;Terraform&lt;/a&gt;, platforms such as &lt;a href="https://docs.doppler.com/docs/github-actions"&gt;GitHub Actions&lt;/a&gt; and &lt;a href="https://docs.doppler.com/docs/vercel"&gt;Vercel&lt;/a&gt;, and cloud secret managers such as &lt;a href="https://docs.doppler.com/docs/aws-secrets-manager"&gt;AWS Secrets Manager&lt;/a&gt; and  &lt;a href="https://docs.doppler.com/docs/azure-key-vault"&gt;Azure Key Vault&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Doppler's Series A is further confirmation that building the first SecretOps Platform is essential to making secrets automation a first-class citizen in the infrastructure management landscape.&lt;/p&gt;




&lt;h2&gt;
  
  
  About CRV
&lt;/h2&gt;

&lt;p&gt;CRV is a venture capital firm that invests in early-stage startups. Since 1970, the firm has invested in more than 500 startups at their most crucial stages, including Airtable, DoorDash, and Iterable. Founders need more than capital to build a great company. It takes a partner who understands the entrepreneurial journey and knows what it takes to win. From founding to IPO and beyond, CRV is there every step of the way. Founders rely on CRV to be trusted, long-term, committed partners, which has helped make CRV into one of the longest-running venture capital firms in the world. Learn more about CRV and the companies shaping the future at &lt;a href="https://www.crv.com"&gt;https://www.crv.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>secretops</category>
      <category>devsecops</category>
    </item>
    <item>
      <title>Why syncing .env files doesn’t scale for secrets management</title>
      <dc:creator>Ryan Blunden</dc:creator>
      <pubDate>Wed, 19 Jan 2022 07:59:09 +0000</pubDate>
      <link>https://forem.com/doppler/why-syncing-env-files-doesnt-scale-for-secrets-management-5325</link>
      <guid>https://forem.com/doppler/why-syncing-env-files-doesnt-scale-for-secrets-management-5325</guid>
      <description>&lt;h2&gt;
  
  
  Learn why using a Universal Secrets Platform is the key to managing environment variables at scale and eliminates the need for syncing .env files.
&lt;/h2&gt;

&lt;p&gt;The benefits of using &lt;a href="https://12factor.net/config" rel="noopener noreferrer"&gt;environment variables to keep secrets out of source code&lt;/a&gt; are well established. But are .env files the best method for managing them?&lt;/p&gt;

&lt;p&gt;Secrets management has &lt;a href="https://blog.doppler.com/why-secrets-management-is-not-a-key-value-store" rel="noopener noreferrer"&gt;evolved beyond the limited Key-Value storage&lt;/a&gt; that .env files provide. However, most developers are either unaware of .env file’s shortcomings or have simply become numb to the pain from the many years of usage and lack of innovation.&lt;/p&gt;

&lt;p&gt;This post aims to highlight the risks of continuing to use .env files, why the major cloud vendors and hosting platforms offer a built-in secrets or environment variables store which can be used instead, and how secrets managers such as &lt;a href="https://thenewstack.io/secrets-management-doppler-or-hashicorp-vault/" rel="noopener noreferrer"&gt;Doppler and HashiCorp Vault&lt;/a&gt; are providing the much-needed management and &lt;a href="https://blog.doppler.com/the-missing-third" rel="noopener noreferrer"&gt;secrets automation layer&lt;/a&gt; on top of encrypted and access controlled secret storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  A brief history of .env files
&lt;/h2&gt;

&lt;p&gt;The usage of environment variables and .env files for application config and secrets largely began around 2013. It was a long overdue and important step towards more secure secrets management practices.&lt;/p&gt;

&lt;p&gt;Libraries such as &lt;a href="https://pypi.org/project/python-dotenv/" rel="noopener noreferrer"&gt;Python’s dotenv&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/dotenv" rel="noopener noreferrer"&gt;Node’s dotenv&lt;/a&gt; made it easy for developers to use .env files and environment variables for application configuration, giving developers a simple path to removing secrets from their source code for good.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1642574498495%2FFQ9CKLwrYa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1642574498495%2FFQ9CKLwrYa.png" alt="Node.js dotenv first commit - July 2013"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1642574768773%2FZ35-1cZtx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1642574768773%2FZ35-1cZtx.png" alt="Python dotenv first commit - June 2013"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To think that .env file usage has remained practically unchanged for over eight years is quite remarkable. It should come as no surprise then, that it’s time to say &lt;a href="https://blog.doppler.com/the-triumph-and-tragedy-of-env-files-zgkyjEpw1pGUVw" rel="noopener noreferrer"&gt;goodbye to .env files&lt;/a&gt; in exchange for alternatives that better meet the needs of modern application development.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problems with .env files
&lt;/h2&gt;

&lt;p&gt;Using .env files allowed us to move secrets out of source code. Unfortunately, they introduced a new set of challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Scaling issues related to syncing .env file changes across environments and different cloud providers, increasing the risk of infrastructure misconfiguration and potential downtime.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easy for .env files to contain syntax errors, requiring additional tools such as &lt;a href="https://github.com/dotenv-linter/dotenv-linter-KWTN2DR" rel="noopener noreferrer"&gt;dotenv-linter&lt;/a&gt; to be added to pro-commit hooks or GitHub checks.&lt;br&gt;&lt;br&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sharing of unencrypted secrets in .env files over Slack when secrets change or new developers join a team risks breaking the principle of least privilege by exposing secrets to potentially unauthorized users.&lt;br&gt;&lt;br&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.doppler.com/blog/goodbye-env-files" rel="noopener noreferrer"&gt;Inconsistent format of environment variables&lt;/a&gt; can cause issues, e.g. Docker and GitHub require unquoted values while other packages do not.&lt;br&gt;&lt;br&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Patchy and inconsistent support for multi-line secrets such as TLS certificates, SSH keys, JSON, and YAML.&lt;br&gt;&lt;br&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Secrets used in multiple applications must be duplicated in every .env file (instead of &lt;a href="https://docs.doppler.com/docs/enclave-secrets#referencing-secrets" rel="noopener noreferrer"&gt;dynamic secrets referencing&lt;/a&gt;), making updating and rolling credentials tedious and repetitive.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If persisted to disk in plain text, they may be readable by unauthorized users with access to the system and threat actors if file restrictive file permissions aren't used.&lt;br&gt;&lt;br&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easy to accidentally expose to &lt;a href="https://www.zdnet.com/article/botnets-have-been-silently-mass-scanning-the-internet-for-unsecured-env-files/" rel="noopener noreferrer"&gt;malicious bots&lt;/a&gt; if placed in the &lt;a href="https://dev.to/ewnx01/env-file-in-public-folder-is-security-risk-59ej-sS_SKFDo7"&gt;webroot of a web server&lt;/a&gt; or &lt;a href="https://trufflesecurity.com/blog/an-s3-bucket-worm-in-the-making-thousands-of-secrets-found-in-open-s3-buckets" rel="noopener noreferrer"&gt;S3 buckets&lt;/a&gt;.&lt;br&gt;&lt;br&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Local development environments break whenever team members forget to share updates that need to be applied to their .env files, e.g. when a feature branch is merged that requires a new secret.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's clear .env files have serious implications for application security Next, we’ll take a closer look at why the productivity impacts of using .env files may be worse than you think.  &lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden productivity costs from using .env files
&lt;/h2&gt;

&lt;p&gt;Small repetitive problems, such as manually updating .env files across several servers as part of the deployment process, while perhaps initially frustrating and annoying, can easily become just an expected part of the application deployment lifecycle.&lt;/p&gt;

&lt;p&gt;While some developers would argue that the papercuts associated with using .env files are minor, one thing we can all agree on is that interruptions can have serious productivity implications for writing code.&lt;/p&gt;

&lt;p&gt;According to a recent study, &lt;a href="https://www.fastcompany.com/944128/worker-interrupted-cost-task-switching" rel="noopener noreferrer"&gt;the average lost time per serious interruption is 23 minutes&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"You need to get into the mindset for development and then slowly trace back to where you left off. This can easily take more than 30 minutes." - Gloria Mark, Professor of Informatics California, Irvine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The cost of misconfiguration errors is not just the time spent fixing an .env file related issue. It's the impact of unexpected context switching and the challenge of getting back into a state of deep work, also known as “flow”.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why developers have ignored traditional secrets managers
&lt;/h2&gt;

&lt;p&gt;Traditional secrets managers such as &lt;a href="https://azure.microsoft.com/en-au/services/key-vault" rel="noopener noreferrer"&gt;Azure Key Vault&lt;/a&gt; or &lt;a href="https://aws.amazon.com/secrets-manager/" rel="noopener noreferrer"&gt;AWS Secrets Manager&lt;/a&gt; provide encrypted storage and fine-grained access controls, specially designed for storing secrets such as API keys, database credentials, SSH keys, and TLS certificates.&lt;/p&gt;

&lt;p&gt;They are incredibly secure, robust, and enterprise-ready. But unfortunately, secret managers such as &lt;a href="https://blog.doppler.com/doppler-vs-hashicorp-vault" rel="noopener noreferrer"&gt;HashiCorp Vault are built for security teams, not developers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As a result, they can be complex to implement correctly and often require secret-fetching implementation details to leak into application code—the exact opposite of the benefits afforded by using language-agnostic environment variables.&lt;/p&gt;

&lt;p&gt;Even security-minded developers motivated to use a traditional secrets managers have typically given up for one primary reason: Using .env files was much easier.&lt;/p&gt;

&lt;p&gt;Instead of environment variables, a vendor-specific SDK, platform integration, or custom application code for fetching secrets from a vendor’s API is often required.&lt;/p&gt;

&lt;p&gt;For example, take this &lt;a href="https://docs.aws.amazon.com/code-samples/latest/catalog/javascript-secrets-secrets_getsecretvalue.js.html" rel="noopener noreferrer"&gt;AWS Secrets Manager SDK for Node.js&lt;/a&gt; sample code for fetching secrets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Load the AWS SDK&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;&amp;lt;{{MyRegionName}}&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;secretName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;&amp;lt;{{MySecretName}}&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;decodedBinarySecret&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Create a Secrets Manager client&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;client&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;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SecretsManager&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;region&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// In this sample we only handle the specific exceptions for the 'GetSecretValue' API.&lt;/span&gt;
&lt;span class="c1"&gt;// See https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html&lt;/span&gt;
&lt;span class="c1"&gt;// We rethrow the exception by default.&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSecretValue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;SecretId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;secretName&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DecryptionFailureException&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// Secrets Manager can't decrypt the protected secret text using the provided KMS key.&lt;/span&gt;
            &lt;span class="c1"&gt;// Deal with the exception here, and/or rethrow at your discretion.&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;InternalServiceErrorException&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// An error occurred on the server side.&lt;/span&gt;
            &lt;span class="c1"&gt;// Deal with the exception here, and/or rethrow at your discretion.&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;InvalidParameterException&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// You provided an invalid value for a parameter.&lt;/span&gt;
            &lt;span class="c1"&gt;// Deal with the exception here, and/or rethrow at your discretion.&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;InvalidRequestException&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// You provided a parameter value that is not valid for the current state of the resource.&lt;/span&gt;
            &lt;span class="c1"&gt;// Deal with the exception here, and/or rethrow at your discretion.&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ResourceNotFoundException&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// We can't find the resource that you asked for.&lt;/span&gt;
            &lt;span class="c1"&gt;// Deal with the exception here, and/or rethrow at your discretion.&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Decrypts secret using the associated KMS CMK.&lt;/span&gt;
        &lt;span class="c1"&gt;// Depending on whether the secret is a string or binary, one of these fields will be populated.&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SecretString&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SecretString&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;buff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Buffer&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="nx"&gt;SecretBinary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;decodedBinarySecret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ascii&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Your code goes here. &lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's this level of complexity compared with using environment variables that turns most developers off from the start.&lt;/p&gt;

&lt;p&gt;As product teams are incentivized to ship software and new features as fast as possible, migrating to a traditional secrets manager usually only occurs due to regulatory requirements or security mandates.&lt;/p&gt;

&lt;p&gt;But is it possible to still use environment variables for modern applications without .env files?&lt;/p&gt;

&lt;h2&gt;
  
  
  Modern platforms with native environment variable storage
&lt;/h2&gt;

&lt;p&gt;Modern hosting platforms such as &lt;a href="https://netlify.com" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;, &lt;a href="https://vercel.com/docs/concepts/projects/environment-variables" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;, &lt;a href="https://docs.digitalocean.com/products/app-platform/how-to/use-environment-variables/" rel="noopener noreferrer"&gt;DigitalOcean&lt;/a&gt;, &lt;a href="https://developers.cloudflare.com/workers/platform/environment-variables" rel="noopener noreferrer"&gt;Cloudflare Workers&lt;/a&gt;, &lt;a href="https://fly.io/docs/reference/secrets/" rel="noopener noreferrer"&gt;Fly.io&lt;/a&gt;, and &lt;a href="https://docs.railway.app/develop/variables" rel="noopener noreferrer"&gt;Railway&lt;/a&gt; all come with secure environment variable storage built-in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1642575262453%2FjLCF9p-T4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1642575262453%2FjLCF9p-T4.png" alt="Cloudflare Workers’ environment variable management UI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This not only shows how easy it is to migrate away from .env files but confirms that environment variables are still the best language and platform-agnostic method for injecting secrets into an application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you need .env files for local development?
&lt;/h2&gt;

&lt;p&gt;It may seem we’re still reliant on .env files for local development if hosting platforms only manage environment variables for applications running on their infrastructure. But this is beginning to change.&lt;/p&gt;

&lt;p&gt;Every developer understands that inconsistencies between local and production environments are a recipe for unexpected issues. That’s why a USP provides first-class support for managing secrets in every environment. A trend modern hosting providers are also beginning to follow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1642575297042%2FLYFYptizC.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1642575297042%2FLYFYptizC.png" alt="Vercel development environment variable UI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vercel.com/docs/concepts/projects/environment-variables#development-environment-variables-CetGQl" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;, for example, offers environment variable storage specifically for local development that is fetched and injected into the Node.js application via the Vercel CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vercel dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But what about developers using hosting providers without such functionality? This is where a USP such as &lt;a href="https://doppler.com/" rel="noopener noreferrer"&gt;Doppler&lt;/a&gt; fills in the gaps, eliminating the need for manually managed .env files.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1642577688122%2FUDNB7cg5S.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1642577688122%2FUDNB7cg5S.png" alt="Doppler project environments"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once developers have &lt;a href="https://docs.doppler.com/docs/create-project" rel="noopener noreferrer"&gt;created a project&lt;/a&gt; and &lt;a href="https://docs.doppler.com/docs/install-cli" rel="noopener noreferrer"&gt;installed the Doppler CLI&lt;/a&gt;, secrets can be injected as environment variables into any application process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doppler run &lt;span class="nt"&gt;--&lt;/span&gt; npm run firebase-local-functions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Developer tooling is rapidly improving to provide a better integrated local development experience that will eliminate the differences between local and production environments and the need for manually managed .env files on developer machines.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Universal approach to taming secret sprawl
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1642575414749%2FAmayL8-jj.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1642575414749%2FAmayL8-jj.jpeg" alt="Doppler Universal Secrets Platform diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.doppler.com/secrets-sprawl-is-hurting-your-productivity-heres-how-to-fix-it" rel="noopener noreferrer"&gt;Taming&lt;/a&gt; &lt;a href="https://blog.doppler.com/secrets-sprawl-is-hurting-your-productivity-heres-how-to-fix-it" rel="noopener noreferrer"&gt;secret sprawl&lt;/a&gt; is a growing challenge for every development team and one that is only made worse as the number of .env files increase. We need an entirely new approach to secrets management that goes beyond incremental improvements—A Universal Secrets Platform.&lt;/p&gt;

&lt;p&gt;Taking a “Universal” approach means being able to manage and sync secrets to every application on any platform by avoiding the problems associated with &lt;a href="https://blog.doppler.com/siloed-secrets-and-security-theatre-why-we-need-universal-secrets-management" rel="noopener noreferrer"&gt;siloed secrets&lt;/a&gt; and antiquated solutions that don’t scale such as trying to sync dotenv files across platforms.&lt;/p&gt;

&lt;p&gt;This can be achieved through a hub-and-spoke model where the USP acts as a single source of truth for secret storage and management with &lt;a href="https://www.doppler.com/integrations" rel="noopener noreferrer"&gt;integrations&lt;/a&gt; automatically syncing secrets when they change to any external platform, including other secrets managers.&lt;/p&gt;

&lt;p&gt;We hope our vision of a &lt;a href="https://blog.doppler.com/siloed-secrets-and-security-theatre-why-we-need-universal-secrets-management#an-automation-driven-approach-universal-secrets-management" rel="noopener noreferrer"&gt;Universal Secrets Platform&lt;/a&gt; serves as inspiration for other secrets managers to create a more developer-friendly experience in order to make migrating away from .env files a more attractive option to developers.&lt;/p&gt;

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

&lt;p&gt;We don’t need to sync .env files. We need the developer-specific workflows that a Universal Secrets Platform such as &lt;a href="https://www.doppler.com" rel="noopener noreferrer"&gt;Doppler&lt;/a&gt; can provide.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1642575672271%2FLMHpHGZS3E.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1642575672271%2FLMHpHGZS3E.jpeg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The simplicity of .env files, while attractive at first, is also its greatest weakness. The demands of modern application development and the explosion of microservices across multiple clouds and platforms provide scalability challenges .env files simply can’t address.&lt;/p&gt;

&lt;p&gt;The use of .env files was certainly an improvement over hard-coded secrets. But better options now exist for secrets management, and not only will your infrastructure be more secure without .env files, you’ll be more productive without them too.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dashboard.doppler.com/register?utm_source=dev&amp;amp;utm_medium=blog&amp;amp;utm_content=why-syncing-env-files-doesnt-scale-for-secrets-management-5325" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flcu1dmvld1wzmz3b0wl0.png" alt="Create your free Doppler account"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>programming</category>
      <category>javascript</category>
      <category>python</category>
    </item>
    <item>
      <title>The Missing Third</title>
      <dc:creator>Brian Vallelunga</dc:creator>
      <pubDate>Wed, 20 Oct 2021 06:30:45 +0000</pubDate>
      <link>https://forem.com/doppler/the-missing-third-50jm</link>
      <guid>https://forem.com/doppler/the-missing-third-50jm</guid>
      <description>&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/636922756" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I believe every developer deserves amazing collaboration tools for managing secrets. Using a secrets manager shouldn't come at the cost of a brain aneurysm. In fact, I think a platform designed for developers of all backgrounds can bring moments of joy to your day, promote healthy security hygiene, and boost your overall developer productivity. &lt;a href="https://doppler.com"&gt;Doppler&lt;/a&gt; was built from the ground up because the &lt;strong&gt;Universal Secrets Platform (USP)&lt;/strong&gt; I needed simply didn't exist. &lt;/p&gt;

&lt;p&gt;I am sure at this point you may be asking who are you? What is a secret - sounds mysterious? What is a USP? How does it differ from a traditional secrets manager or even a dotenv file? Lots of questions, I’ll start off with a simple one - who am I. &lt;/p&gt;

&lt;p&gt;Hi - I am Brian 👋, an introvert who plays an extrovert in real life. I love building things (mostly through code and legos), have a passion for making weird art, and hanging with friends. I am sharing all this to show I am just another person, another developer, like you. Below are my thoughts on how the current way of managing secrets is broken and a proposed solution through what I call a &lt;strong&gt;Universal Secrets Platform (USP).&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  App = Code + Compute + Secrets
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;From my experience, running an application typically requires at the minimum code, compute, and secrets. Three pieces of a puzzle that form a living picture.&lt;/strong&gt; We are lucky to live at a time where amazing collaborative source control platforms exist enabling distributed teams to build together. This paired with CI/CD and cloud-based hosting allows for fully automated deployments that can reach users in most regions at near infinite scale. But what about the other, often deemed less exciting piece to running applications — secrets?&lt;/p&gt;

&lt;p&gt;For context, secrets are typically API keys, database urls, certificates, environment variables, and other app configuration such as a port variable. They are often used to grant access to highly sensitive data/services and thus should never be stored in an unencrypted format such as in code or dotenv file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Responsibility to our users
&lt;/h3&gt;

&lt;p&gt;Secrets are the literal keys to our digital kingdom and deserve to be treated as such. Before Doppler I didn't think much about secrets, as my job was to ship features to customers as fast as possible. Building and shipping is an adrenaline rush, and through that rush, it became easy to lose sight of what I was doing. &lt;/p&gt;

&lt;p&gt;I thought of secrets as a means to an end, a required piece of the puzzle that let my code talk to services like Stripe and Twilio. I never stopped to think about the impact it would have if those secrets ever became "not so secret". In one decision, made now or years ago, I could have accidentally leaked all of our users data that had been entrusted with us. I often would store our production Stripe key in an unencrypted dotenv file which granted access to our customer's credit cards and bank accounts. The impact that one secret could have had on their lives if it ever got leaked would have been tremendous.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's easy to forget that behind the UUID of a user is a real person with a life and loved ones. They trust us with their data, the least we should do is protect.&lt;/strong&gt; While you might be thinking that a leak would never happen to you, the Twitch source code leak was a brutal reminder that the unexpected can happen at any time, to anyone. I hope this gives you a moment of pause to reframe how you think about the secrets you already have today.&lt;/p&gt;

&lt;p&gt;.... a minute later. Pause is over, onwards!&lt;/p&gt;

&lt;h3&gt;
  
  
  A missing puzzle piece
&lt;/h3&gt;

&lt;p&gt;It would be painful to imagine a world before the likes of GitHub, where you had to take code and share it over email/FTP and then perform surgical merge resolution to adopt the changes. This wouldn’t just happen once, but every time anyone changed code. Then to deploy that newly minted code to production, one person would need to SSH into a server and upload the code manually. Hoping and praying along the way that they didn’t mess up and accidentally bring the service down. These workflows were manual because we didn’t have pull requests, CICD, infrastructure as code, cloud computing, or other fancy automations we benefit from today.&lt;/p&gt;

&lt;p&gt;But everything isn’t as automated as it seems, as much of the same problems exist today with secrets. Take dotenv for example… which is a file that holds a list of secrets in an unencrypted (🤦) human-readable format. Each developer and every environment (e.g. staging, production) needs its own dedicated dotenv file that is specific for that machine. This file should never be tracked by source control, leaving developers to the manual, error-prone, and time-consuming dynamics that we previously experienced with code before source control existed. These unencrypted files are then passed around Slack, email, other productivity tools that are not designed to store the literal keys to the kingdom.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fzWQpdpB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fg0nn8v47n30wz6qoibv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fzWQpdpB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fg0nn8v47n30wz6qoibv.png" alt="Before Universal Secrets Platform"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then when updating the ".env" file for production and other environments, the developer has to add the secrets manually. In the case of a medium to large company, they would need to create a task/ticket for the DevOps team who is responsible for managing infrastructure. &lt;/p&gt;

&lt;p&gt;Environments must then be updated in a timely manner as the secrets need to be available before the new code that uses them is deployed. This creates a tricky race condition when code is automatically pushed from GitHub while secrets are configured by humans. There is also a greater chance of the service going down when secrets are touched manually as human error can strike in the moment through a seemingly innocuous typo. Secrets need to be perfect, if they are off by even a single character from what they need to be, it can cause serious problems.&lt;/p&gt;

&lt;p&gt;Adding or updating a secret in a dotenv file may sound simple, but in reality, it’s much more complex. Changes are often made in groups where some secrets are added, while others are removed or even updated. Imagine distributing all of those changes by hand day in and day out. Sounds like a recipe for a typo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automated and collaborative tooling
&lt;/h3&gt;

&lt;p&gt;At this point you may be asking why there isn't a tool that already exists since the conventional way is so painful. Truth be told, I asked the same question before I started Doppler. To my surprise, there are tools that exist but I hadn't heard of them because they weren't built for me. I am a developer, heart and soul, and those tools were built for the security teams. I found the user experience sucked, SDKs complex, on-premise requirements frustrating, and lack of built-in management features left the developer experience out of the equation while ticking all the right boxes for security. &lt;/p&gt;

&lt;p&gt;I am a visual person, I want a dashboard. I don’t like repeating high-risk mundane tasks. I want integrations like GitHub where my secrets are automatically deployed alongside my code. Lastly I am human, I forget things and make typos. I want a tool that has my back by catching my mistakes before they become costly. And in the event I do mess up, I should be able to instantly roll back the changes from a click in a dashboard. I want the automation benefits that have revolutionized how we build and ship software applied to the one area that developer tools forgot—secrets.  &lt;/p&gt;

&lt;p&gt;We as an industry need to define a new devtools category that captures what we as a development community need for managing secrets securely, from individual developers to enterprise organizations.&lt;/p&gt;

&lt;p&gt;I propose that we need a &lt;strong&gt;Universal Secrets Platform (USP)&lt;/strong&gt;. A single source of truth for developers and their teams to manage, store, and automate secrets syncing to every major hosting platform and cloud provider. Universal is the differentiating prefix and comes with four requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Universally&lt;/strong&gt; accessible and useable for developers of all skill levels and specialties. From junior developers to the most senior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Universally&lt;/strong&gt; empowers least privilege access. Developers and machines should only have access to the secrets they need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Universally&lt;/strong&gt; provides a full-featured and easy-to-use dashboard and CLI that takes minutes, not days or weeks to learn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Universally&lt;/strong&gt; supports every operating system and infrastructure, from Serverless to Containers, static-sites to Virtual Machines, and everything in-between.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3HK01eb8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1d7okps2x3whd7nnb7c9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3HK01eb8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1d7okps2x3whd7nnb7c9.png" alt="After Universal Secrets Platform.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So how does this differ from the secrets managers that exist today? AWS and GCP both have secrets management offerings while new cloud providers like Vercel and Netlify also allow for securely storing secrets. To see the difference, we need to separate storage from management to match reality. The secret “managers” we have today actually only offer simple Key-Value storage and lack support for the workflows developers need in practice. In our new model, we should treat them as storage destinations. The USP becomes the central source of truth, responsible for distributing secrets to the corresponding storage destinations. This also serves as a multi-cloud solution for a modern-day data center.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2eipKriF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/02rw26x98wzpjaatbnjc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2eipKriF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/02rw26x98wzpjaatbnjc.png" alt="Configuration Layout"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s take a look at an example through the lens of a project. Straight off, we notice that every environment, including local development, is supported. Not only that, but every developer gets their own isolated secrets store. There is automated testing in place through CircleCI while Netlify is used staging and production deployments. Each one of these environments (development, staging, and production) has at least one destination ranging from a developer’s laptop to CircleCI and Netlify. &lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Universal Secrets Platform&lt;/strong&gt; is the hub where all those secrets are stored, managed, and versioned across environments. When they are changed in the USP, secrets automation updates them in each linked platform (CircleCI and Netlify in this case) with the option of automatically triggering a rebuild or redeploy. This model mirrors what source control platforms provide today. When code is pushed to a branch, GitLab pushes those changes to the destination infrastructure such as AWS. &lt;/p&gt;

&lt;h3&gt;
  
  
  Measuring success
&lt;/h3&gt;

&lt;p&gt;As an industry, we will have a strong signal that this category is going to be successful when developers treat it like they would a source control platform. As essential as code reviews through pull requests are in today’s software development life cycle, so too will be managing secrets at the cloud and platform level. From the collaborative features enabling distributed teams to work effectively together, to the automations that enable CICD workflows, there is a lot left to do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use a Universal Secrets Platform
&lt;/h3&gt;

&lt;p&gt;My hope is that you as a developer walk away from this long-winded article looking to add a &lt;strong&gt;Universal Secrets Platform&lt;/strong&gt; to your toolbox. It's a completely different way of thinking about managing secrets, but just like when GitHub first pioneered the notion of a 'pull request’, once you see the benefits, there's no turning back. Together let's make the internet a more secure place for the projects we work on and the users they serve.&lt;/p&gt;

</description>
      <category>security</category>
      <category>cloud</category>
      <category>productivity</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>Doppler: How to Set Environment Variables for a Python Django Application using Apache and mod_wsgi in Docker</title>
      <dc:creator>Ryan Blunden</dc:creator>
      <pubDate>Tue, 17 Aug 2021 07:00:00 +0000</pubDate>
      <link>https://forem.com/doppler/doppler-how-to-set-environment-variables-for-a-python-django-application-using-apache-and-modwsgi-in-docker-3oh1</link>
      <guid>https://forem.com/doppler/doppler-how-to-set-environment-variables-for-a-python-django-application-using-apache-and-modwsgi-in-docker-3oh1</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CjRMKidT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vqn44aw7amct38itfxaz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CjRMKidT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vqn44aw7amct38itfxaz.jpg" alt="How to Set Environment Variables for a Python Django Application using Apache and mod_wsgi in Docker" width="880" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using environment variables to configure Django and other Python applications is awesome, but using them with Apache and mod_wsgi in Docker is a tricky thing to get right.&lt;/p&gt;

&lt;p&gt;That's why I created this step-by-step tutorial and &lt;a href="https://file+.vscode-resource.vscode-webview.net/Users/rb/Projects/django-apache-mod-wsgi/%5Bhttps://github.com/DopplerUniversity/django-apache-mod-wsgi%5D"&gt;sample application&lt;/a&gt; to put all the info you need in one place.&lt;/p&gt;

&lt;p&gt;Although this tutorial is for Docker and Django, the same steps apply, whether you're using a Virtual Machine or a different Python framework.&lt;/p&gt;

&lt;p&gt;Prefer just to read the code? Head to the accompanying repository at &lt;a href="https://github.com/DopplerUniversity/django-apache-mod-wsgi"&gt;https://github.com/DopplerUniversity/django-apache-mod-wsgi&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment Variables, Apache and mod_wsgi
&lt;/h2&gt;

&lt;p&gt;When hosting a Python WSGI compatible framework like Django in Apache with mod_wsgi, the only environment variables populated in the &lt;strong&gt;os.environ&lt;/strong&gt; dictionary are those that exist in the environment of the script that starts Apache. But instead of having to mess with Apache's service manager settings (e.g. &lt;strong&gt;systemd&lt;/strong&gt; or &lt;strong&gt;systemctl&lt;/strong&gt;), there's a better way.&lt;/p&gt;

&lt;p&gt;Most Apache distributions provide a shell script specifically for the purpose of setting environment variables that will be made available to modules such as mod_wsgi.&lt;/p&gt;

&lt;p&gt;It's then a matter of knowing the location of this shell script as it can be different depending on the Linux distribution. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Debian/Ubuntu: &lt;strong&gt;/etc/apache2/envvars&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  CentOS: &lt;strong&gt;/etc/sysconfig/httpd&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll be using the &lt;a href="https://github.com/docker-library/python/blob/master/3.9/buster/slim/Dockerfile"&gt;python:3.9-slim-buster Docker image&lt;/a&gt; is Debian based.&lt;/p&gt;

&lt;h2&gt;
  
  
  Appending App Config and Secrets to the Environment Variables File
&lt;/h2&gt;

&lt;p&gt;Essentially, it boils down to fetching the secrets as key/value pairs and writing them to the &lt;strong&gt;envvars&lt;/strong&gt; file in the typical shell environiment variables format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;FIRST_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"The"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LAST_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Mandalorion"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But where from and how do we fetch the app config and secrets to populate that file?&lt;/p&gt;

&lt;p&gt;As I'm the Developer Advocate for Doppler, I'll start with a &lt;a href="https://docs.doppler.com/docs/enclave-installation"&gt;Doppler CLI&lt;/a&gt; example, but the mechanics of "fetch secrets, then append to file" can easily be adapted.&lt;/p&gt;

&lt;p&gt;First, you would need to set up your project in Doppler and you can use the following button to get you started if you want to follow along.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dashboard.doppler.com/workplace/template/import?template=https%3A%2F%2Fgithub.com%2FDopplerUniversity%2Fdjango-apache-mod-wsgi%2Fblob%2Fmain%2Fdoppler-template.yaml&amp;amp;_ga=2.263293196.1681842210.1630885780-1034093108.1623672779"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I-IDcarx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/DopplerUniversity/app-config-templates/main/doppler-button.svg" alt="Import to Doppler" width="176" height="40"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then use the Doppler CLI inside the Docker container to fetch the secrets (requires a &lt;strong&gt;DOPPLER_TOKEN&lt;/strong&gt; environment variable with a &lt;a href="https://docs.doppler.com/docs/enclave-service-tokens"&gt;Service Token&lt;/a&gt; value):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Transform JSON key:value pairs into export statements using jq&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOPPLER_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'[info]: Appending environment variables to /etc/apache/envvars using Doppler CLI'&lt;/span&gt;
    doppler secrets download &lt;span class="nt"&gt;--no-file&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;$'. | to_entries[] | "export &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="s1"&gt;.key)=&lt;/span&gt;&lt;span class="se"&gt;\'\(&lt;/span&gt;&lt;span class="s1"&gt;.value)&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/apache2/envvars
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice I've used single quotes, not double quotes around the values?&lt;/p&gt;

&lt;p&gt;That's because it gives you the flexibility of storing secrets with double quotes such as JSON in Doppler which you could use for example, to &lt;a href="https://github.com/DopplerUniversity/django-apache-mod-wsgi/blob/main/src/doppler/settings.py#L29"&gt;dynamically set Django's ALLOWED_HOSTS&lt;/a&gt; setting dynamically for any environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ALLOWED_HOSTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'ALLOWED_HOSTS'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could also use a &lt;strong&gt;.env&lt;/strong&gt; file but &lt;a href="https://www.doppler.com/blog/the-triumph-and-tragedy-of-env-files"&gt;I wouldn't recommend it&lt;/a&gt; and instead, I'd look into using a &lt;a href="https://www.doppler.com/blog/what-is-a-secrets-manager"&gt;secrets manager&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But that aside, here is how you could do it using an &lt;strong&gt;.env&lt;/strong&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/.env"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'[info]: Appending environment variables to /etc/apache/envvars from .env file'&lt;/span&gt;
    &lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/.env"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/apache2/envvars
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we know how to pass environment variables from Apache to mod_wsgi, let's move onto getting this working in Docker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker Configuration for Apache and mod_wsgi
&lt;/h2&gt;

&lt;p&gt;Let's breakdown the task of configuring a Python Django Application using Apache and mod_wsgi in Docker into three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Custom Start Script&lt;/li&gt;
&lt;li&gt; Apache Site Config&lt;/li&gt;
&lt;li&gt; Dockerfile&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you only want to see the working code examples, head to the accompanying repository at &lt;a href="https://github.com/DopplerUniversity/django-apache-mod-wsgi"&gt;https://github.com/DopplerUniversity/django-apache-mod-wsgi&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As this isn't a Docker or Apache tutorial, I won't be diving too deeply into the Dockerfile or Apache site config file, but if you've got questions, head over to the &lt;a href="https://community.doppler.com/"&gt;Doppler community forum&lt;/a&gt; and I'll be able to help you there.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Custom Start Script
&lt;/h3&gt;

&lt;p&gt;Running your application in Docker is usually a case of setting the &lt;strong&gt;CMD,&lt;/strong&gt; for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "src/app.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it's trickier here as we first need to append the environment variables to &lt;code&gt;/etc/apache2/envvars&lt;/code&gt; before running Apache.&lt;/p&gt;

&lt;p&gt;As this requires multiple commands, we'll create a custom script:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c"&gt;# apache-doppler-start&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'ServerName localhost'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/apache2/apache2.conf &lt;span class="c"&gt;# Silence FQDN warning&lt;/span&gt;

&lt;span class="c"&gt;# Doppler CLI&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOPPLER_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'[info]: Appending environment variables to /etc/apache/envvars from Doppler CLI'&lt;/span&gt;
    doppler secrets download &lt;span class="nt"&gt;--no-file&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;$'. | to_entries[] | "export &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="s1"&gt;.key)=&lt;/span&gt;&lt;span class="se"&gt;\'\(&lt;/span&gt;&lt;span class="s1"&gt;.value)&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/apache2/envvars
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Mounted .env file&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/.env"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'[info]: Appending environment variables to /etc/apache/envvars from .env file'&lt;/span&gt;
    &lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/.env"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/apache2/envvars
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Run Apache&lt;/span&gt;
apache2ctl &lt;span class="nt"&gt;-D&lt;/span&gt; FOREGROUND
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Apache Site Config
&lt;/h3&gt;

&lt;p&gt;Here is an example Apache site config file for a Django application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# wsgi.conf
&lt;/span&gt;&amp;lt;&lt;span class="n"&gt;VirtualHost&lt;/span&gt; *:&lt;span class="m"&gt;80&lt;/span&gt;&amp;gt;
    &lt;span class="n"&gt;ServerName&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;-&lt;span class="n"&gt;apache&lt;/span&gt;-&lt;span class="n"&gt;mod&lt;/span&gt;-&lt;span class="n"&gt;wsgi&lt;/span&gt;
    &lt;span class="n"&gt;ServerAlias&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;-&lt;span class="n"&gt;apache&lt;/span&gt;-&lt;span class="n"&gt;mod&lt;/span&gt;-&lt;span class="n"&gt;wsgi&lt;/span&gt;
    &lt;span class="n"&gt;ServerAdmin&lt;/span&gt; &lt;span class="n"&gt;webmaster&lt;/span&gt;@&lt;span class="n"&gt;doppler&lt;/span&gt;

    &lt;span class="c"&gt;# Defining `WSGIDaemonProcess` and `WSGIProcessGroup` triggers daemon mode
&lt;/span&gt;    &lt;span class="n"&gt;WSGIDaemonProcess&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;-&lt;span class="n"&gt;apache&lt;/span&gt;-&lt;span class="n"&gt;mod&lt;/span&gt;-&lt;span class="n"&gt;wsgi&lt;/span&gt; &lt;span class="n"&gt;processes&lt;/span&gt;=&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="n"&gt;threads&lt;/span&gt;=&lt;span class="m"&gt;15&lt;/span&gt; &lt;span class="n"&gt;display&lt;/span&gt;-&lt;span class="n"&gt;name&lt;/span&gt;=%{&lt;span class="n"&gt;GROUP&lt;/span&gt;} &lt;span class="n"&gt;python&lt;/span&gt;-&lt;span class="n"&gt;path&lt;/span&gt;=/&lt;span class="n"&gt;usr&lt;/span&gt;/&lt;span class="n"&gt;local&lt;/span&gt;/&lt;span class="n"&gt;lib&lt;/span&gt;/&lt;span class="n"&gt;python3&lt;/span&gt;.&lt;span class="m"&gt;9&lt;/span&gt;/&lt;span class="n"&gt;site&lt;/span&gt;-&lt;span class="n"&gt;packages&lt;/span&gt;:/&lt;span class="n"&gt;usr&lt;/span&gt;/&lt;span class="n"&gt;src&lt;/span&gt;/&lt;span class="n"&gt;app&lt;/span&gt;    
    &lt;span class="n"&gt;WSGIProcessGroup&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;-&lt;span class="n"&gt;apache&lt;/span&gt;-&lt;span class="n"&gt;mod&lt;/span&gt;-&lt;span class="n"&gt;wsgi&lt;/span&gt;
    &lt;span class="n"&gt;WSGIScriptAlias&lt;/span&gt; / /&lt;span class="n"&gt;usr&lt;/span&gt;/&lt;span class="n"&gt;src&lt;/span&gt;/&lt;span class="n"&gt;app&lt;/span&gt;/&lt;span class="n"&gt;doppler&lt;/span&gt;/&lt;span class="n"&gt;wsgi&lt;/span&gt;.&lt;span class="n"&gt;py&lt;/span&gt;

    &amp;lt;&lt;span class="n"&gt;Directory&lt;/span&gt; /&lt;span class="n"&gt;usr&lt;/span&gt;/&lt;span class="n"&gt;src&lt;/span&gt;/&lt;span class="n"&gt;app&lt;/span&gt;/&lt;span class="n"&gt;doppler&lt;/span&gt;/&amp;gt;
        &amp;lt;&lt;span class="n"&gt;Files&lt;/span&gt; &lt;span class="n"&gt;wsgi&lt;/span&gt;.&lt;span class="n"&gt;py&lt;/span&gt;&amp;gt;
            &lt;span class="n"&gt;Require&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;granted&lt;/span&gt;
        &amp;lt;/&lt;span class="n"&gt;Files&lt;/span&gt;&amp;gt;
    &amp;lt;/&lt;span class="n"&gt;Directory&lt;/span&gt;&amp;gt;

    &lt;span class="c"&gt;# Redirect all logging to stdout for Docker
&lt;/span&gt;    &lt;span class="n"&gt;LogLevel&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt;
    &lt;span class="n"&gt;ErrorLog&lt;/span&gt; /&lt;span class="n"&gt;dev&lt;/span&gt;/&lt;span class="n"&gt;stdout&lt;/span&gt;
    &lt;span class="n"&gt;TransferLog&lt;/span&gt; /&lt;span class="n"&gt;dev&lt;/span&gt;/&lt;span class="n"&gt;stdout&lt;/span&gt;
&amp;lt;/&lt;span class="n"&gt;VirtualHost&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Dockerfile
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Dockerfile&lt;/strong&gt; is reasonably straightforward, installing the Doppler CLI and Apache dependencies before copying the Django source code, custom script, and Apache site config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.9-slim-buster&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PYTHONUNBUFFERED 1&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PYTHONDONTWRITEBYTECODE 1&lt;/span&gt;

&lt;span class="c"&gt;# Install Doppler CLI and related dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nt"&gt;-qq&lt;/span&gt; update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; apt-transport-https ca-certificates curl gnupg jq &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;curl &lt;span class="nt"&gt;-sLf&lt;/span&gt; &lt;span class="nt"&gt;--retry&lt;/span&gt; 3 &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.2 &lt;span class="nt"&gt;--proto&lt;/span&gt; &lt;span class="s2"&gt;"=https"&lt;/span&gt; &lt;span class="s1"&gt;'https://packages.doppler.com/public/cli/gpg.DE2A7741A397C129.key'&lt;/span&gt; |  apt-key add - &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb https://packages.doppler.com/public/cli/deb/debian any-version main"&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; /etc/apt/sources.list.d/doppler-cli.list &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;apt-get &lt;span class="nt"&gt;-qq&lt;/span&gt; update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install &lt;/span&gt;doppler

&lt;span class="c"&gt;# Install Apache and related dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--yes&lt;/span&gt; apache2 apache2-dev libapache2-mod-wsgi-py3 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get clean &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get remove &lt;span class="nt"&gt;--purge&lt;/span&gt; &lt;span class="nt"&gt;--auto-remove&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements*.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--quiet&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; pip &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--quiet&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# Application source&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src/ ./&lt;/span&gt;

&lt;span class="c"&gt;# Custom CMD script&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; apache-doppler-start /usr/local/bin/&lt;/span&gt;

&lt;span class="c"&gt;# Apache site config&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; wsgi.conf /etc/apache2/sites-enabled/000-default.conf&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80 443&lt;/span&gt;

&lt;span class="c"&gt;# https://httpd.apache.org/docs/2.4/stopping.html#gracefulstop&lt;/span&gt;
&lt;span class="k"&gt;STOPSIGNAL&lt;/span&gt;&lt;span class="s"&gt; SIGWINCH&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["apache-doppler-start"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With all the pieces in place, we can nowbuild the Docker image (clone the &lt;a href="https://github.com/DopplerUniversity/django-apache-mod-wsgi"&gt;sample repository&lt;/a&gt; to follow along):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker image build &lt;span class="nt"&gt;-t&lt;/span&gt; django-apache-mod-wsgi:latest &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're ready to run the container!&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the Django Application with Apache and mod_wsgi in Docker
&lt;/h2&gt;

&lt;p&gt;We'll start with a Doppler example, then with an &lt;strong&gt;.env&lt;/strong&gt; file.&lt;/p&gt;

&lt;p&gt;With Doppler, you'll first need to set a &lt;strong&gt;DOPPLER_TOKEN&lt;/strong&gt; environment variable to the value of a &lt;a href="https://docs.doppler.com/docs/enclave-service-tokens"&gt;Service Token&lt;/a&gt;. This is what provides read-only access to a specific Doppler config in production environments.&lt;/p&gt;

&lt;p&gt;Usually, this would be securely set by your deployment environment (e.g. GitHub Action Secret) but for completeness and simplicity, we'll set it manually below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DOPPLER_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"dp.st.xxxx"&lt;/span&gt; &lt;span class="c"&gt;# Service token value created from Doppler dashboard&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker container run &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--init&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--name&lt;/span&gt; doppler-apache-mod-wsgi &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;DOPPLER_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOPPLER_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    django-apache-mod-wsgi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  .env File
&lt;/h3&gt;

&lt;p&gt;To run the &lt;strong&gt;.env&lt;/strong&gt; file version, we'll use the &lt;strong&gt;sample.env file&lt;/strong&gt; from the &lt;a href="https://github.com/DopplerUniversity/django-apache-mod-wsgi/blob/main/sample.env"&gt;sample repository&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# sample.env&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DJANGO_SETTINGS_MODULE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'doppler.settings'&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'yes'&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ALLOWED_HOSTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'["*"]'&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'bf5e1b31-6ba7-48e2-9175-f2293671e6df'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then to run the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker container run &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--init&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--name&lt;/span&gt; dotenv-apache-mod-wsgi &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/sample.env:/usr/src/app/.env &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="se"&gt;\&lt;/span&gt;
    django-apache-mod-wsgi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Nice work in making it to the end!&lt;/p&gt;

&lt;p&gt;Now you know how to configure Python applications hosted with Apache and mod_wsgi running in Docker using environment variables for app configuration and secrets.&lt;/p&gt;

&lt;p&gt;Feedback is welcome and you can reach us on &lt;a href="https://twitter.com/dopplerhq"&gt;Twitter&lt;/a&gt;, our &lt;a href="https://community.doppler.com/"&gt;Community forum&lt;/a&gt;, or send me an email at &lt;a href="//mailto:ryan.blunden@doppler.com"&gt;ryan.blunden@doppler.com&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dashboard.doppler.com/register?utm_source=dev&amp;amp;utm_medium=blog&amp;amp;utm_content=doppler-how-to-set-environment-variables-for-a-python-django-application-using-apache-and-modwsgi-in-docker-3oh1"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VldNMgYL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lcu1dmvld1wzmz3b0wl0.png" alt="Create your free Doppler account" width="880" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>apache</category>
      <category>docker</category>
    </item>
    <item>
      <title>Why Secrets Management Is NOT Just a Key-Value Store</title>
      <dc:creator>Ryan Blunden</dc:creator>
      <pubDate>Tue, 10 Aug 2021 00:28:34 +0000</pubDate>
      <link>https://forem.com/doppler/why-secrets-management-is-not-just-a-key-value-store-27i4</link>
      <guid>https://forem.com/doppler/why-secrets-management-is-not-just-a-key-value-store-27i4</guid>
      <description>&lt;p&gt;For those who’ve managed secrets via .env files or have briefly looked at secrets managers such as  &lt;a href="https://www.doppler.com/blog/doppler-vs-hashicorp-vault"&gt;HashiCorp Vault&lt;/a&gt; , it’s easy to get the impression that “secrets management” is simply the storage and retrieval of secrets from a Key-Value data store.&lt;/p&gt;

&lt;p&gt;But recurring tasks such as instantly comparing secret values between environments when troubleshooting, or being alerted when a new secret is introduced are crucial features every secrets manager should provide. Without them, development teams are forced to cobble together their own solutions, often in silos on a per-application basis.&lt;/p&gt;

&lt;p&gt;The reality is that .env files and traditional secrets managers were never designed to meet the needs of modern application development teams, where microservices and multi-cloud deployments are the new normal.&lt;/p&gt;

&lt;p&gt;Essentially, we want the flexibility of a Key-Value secret storage solution but with an operating model and feature set that decreases complexity, increases developer productivity, and standardizes how secrets are managed for applications in every part of the business.&lt;/p&gt;

&lt;p&gt;In this post, we’ll explore the most important aspects of secrets management and why Key-Value storage is only the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Access Control and Permissions
&lt;/h2&gt;

&lt;p&gt;Who needs access to secrets? What should their level of permissions be and which applications and environments should they have access to? Is access provided according to org structure or application ownership?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qdgOUrjZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1631057618522/BmJjxHL1l.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qdgOUrjZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1631057618522/BmJjxHL1l.jpeg" alt="" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If access controls aren’t fine-grained enough, you risk violating the principle of least privilege but if too rigid, can impede the ability of teams to embrace a culture of DevOps and “shifting left” to bring security earlier into the application development process.&lt;/p&gt;

&lt;p&gt;The following is a starting point for secret and access permission requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Ability to segment parts of the business into separate workplaces so for example, an acquired start-up can onboard to using the secrets manager while remaining entirely separate from the organization’s existing secrets.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consider how access will be managed across cloud and platform boundaries, e.g. AWS, GCP, and Vercel?&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ability to grant access to secrets for specific applications and environments within each application.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In most cases, developers should only have access to secrets belonging to their applications and only for the appropriate environment (e.g., dev and test). DevSecOps will have greater permission levels so that they can work cross-functionally in any environment.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Temporary secrets access to external entities (e.g. contractors) should be scoped to a subset of application secrets via an auth/service token.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;One-off sharing of secrets to external entities (e.g. TLS Certificates to an external firm) should be captured in an activity log and sent via encrypted means using a service such as &lt;a href="https://share.doppler.com/"&gt;Doppler Share&lt;/a&gt;.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mandated MFA for secrets manager dashboard access.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Privilege elevation (e.g. developer viewing production secrets) be time-limited and captured in an activity log.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Seamless integration with existing Single Sign-on solutions such as SAML and SCIM to allow default access permission levels to be assigned based on role and/or group membership.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A further consideration is restricting machine access to a CIDR IP address range, preventing leaked service tokens from use outside of trusted corporate and cloud networks.&lt;/p&gt;

&lt;p&gt;There’s a lot to consider for access control and permissions and your secrets manager should make it straightforward to meet these requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Versioning and Rollback
&lt;/h2&gt;

&lt;p&gt;Versioning and the ability to roll back changes is a must and shouldn’t come at the cost of reduced performance or additional storage fees.&lt;/p&gt;

&lt;p&gt;While most secret managers support versioning and rollback, it’s critical to evaluate the actual process.&lt;/p&gt;

&lt;p&gt;Being able to roll back a misconfigured change in a single click with support for triggering an automatic application reload or deploy is something teams should come to expect from a secrets manager, enabling a misconfiguration fix to be applied in seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secret Documentation
&lt;/h2&gt;

&lt;p&gt;There are wildly different perspectives on documenting code, but the one thing all developers can agree upon is that useful code comments provide context that explains the “why”, not just what the code does.&lt;/p&gt;

&lt;p&gt;Such context and comments can be especially valuable for secrets, but where should this be documented?&lt;/p&gt;

&lt;p&gt;A reasonable option is a README or inline code comments where the secret is in use, however, the context may be lost if a secret’s value is changed in your secrets manager by someone who hasn’t viewed the code.&lt;/p&gt;

&lt;p&gt;Being able to notate secrets directly in the secrets manager reduces the chance of misconfiguration errors that could’ve been easily avoided, had the appropriate comments (and context) be provided.&lt;/p&gt;

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

&lt;p&gt;The closer your development environment is to production, the less likely unexpected problems are to arise during deployment, which is why secrets should be accessed the same way in development, as they are in production.&lt;/p&gt;

&lt;p&gt;Your secrets manager should treat Development as a proper environment in its own right and be responsible for supplying secrets to developers locally. This saves each developer from manually updating and patching their own environment variables list in their IDE or .env file.&lt;/p&gt;

&lt;p&gt;Development environments also present a unique challenge where secret values will often be specific to each developer, e.g. when working on a feature branch that uses a new secret.&lt;/p&gt;

&lt;p&gt;Developers need the flexibility to override secrets during development without affecting other developers on the team. This is an essential feature a secrets manager should provide to ensure secrets access remains consistent for every environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Single Unified View for Application Configuration
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b5ja_SSc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1631057620203/Rs6Mz_GQf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b5ja_SSc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1631057620203/Rs6Mz_GQf.png" alt="" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the reasons developers love .env files is that it gives them a single unified view of how an application is configured as they tend to hold both secrets and config data such as API keys, port bindings, and feature flags.&lt;/p&gt;

&lt;p&gt;When assessing the transition from .env files to a secrets manager, teams are often confused as to whether secrets and config should be separated. For example, should secrets be migrated to the secrets manager while config continues to exist in .env files?&lt;/p&gt;

&lt;p&gt;While secret managers are designed to store sensitive data, it makes sense to store both config and secrets together, as there is no logistical or financial advantage gained by separating them. &lt;/p&gt;

&lt;p&gt;If secrets and config are simply displayed as Key-Value pairs in the secrets manager’s dashboard, it’s up to the viewer to define a way to visually organize secrets by application, then further segment into separate environments.&lt;/p&gt;

&lt;p&gt;Your secrets manager should provide a simple built-in mechanism to view secrets for a specific application and environment, and storing config as well as secrets in your secret manager ensures you’ll still get a single unified view of how your application is configured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reduce Friction to Increase Adoption
&lt;/h2&gt;

&lt;p&gt;While implementing a secrets manager is often a security endeavor, it's better viewed as a productivity one, as every project team can appreciate time and effort saved. And if security posture is improved, well even better!&lt;/p&gt;

&lt;p&gt;But if the focus is on security at the cost of productivity (even if short term), gaining widespread adoption will be significantly more difficult, as product teams are incentivized to ship new features, not improve security (unless mandated to do so).&lt;/p&gt;

&lt;p&gt;Therefore, the secrets manager that is likely to have the biggest impact in improving security, is the one that provides a nearly frictionless experience for teams to implement and use on a daily basis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preventing Misconfiguration Issues
&lt;/h2&gt;

&lt;p&gt;Misconfiguration issues are much easier to deal with if caught before changes are applied in production. To achieve this, developers and DevOps Engineers need access to a secrets activity log that pushes secret events to where they naturally communicate such as a Slack or Microsoft Teams channel.&lt;/p&gt;

&lt;p&gt;The secrets management dashboard should also provide indicators of potential problems such as if a secret was added to the staging environment but not production, as well as troubleshooting features such as the ability to instantly compare a secret value across every environment.&lt;/p&gt;

&lt;p&gt;A secrets manager must therefore help to prevent known issues associated with application configuration and deployment scenarios to increase production stability and reduce time spent firefighting issues caused by misconfiguration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automate Application Reload and Redeployment
&lt;/h2&gt;

&lt;p&gt;The Infrastructure as Code movement put automation at the heart of deployment workflows and an essential feature of any secrets manager should be the option to automatically trigger an application to reload or redeploy if its config or secrets change.&lt;/p&gt;

&lt;p&gt;This again highlights the need for secret managers to go beyond simply Key-Value storage, as, as they must know which application to trigger a redeployment for, based on the application and environment the changed secret belongs to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secrets Management Goes Beyond Secret Storage
&lt;/h2&gt;

&lt;p&gt;By now, it should be clear that secrets management extends beyond the secure storage of secrets as Key-Value pairs. We’ve touched on processes, best practices, as well as various risks, challenges, and issues that can arise and what a secrets manager should reasonably do to mitigate them.&lt;/p&gt;

&lt;p&gt;These relate to key elements of storage, access, visibility, integrations, workflows, and troubleshooting processes for not just secrets management, but application configuration generally.&lt;/p&gt;

&lt;p&gt;Doppler is simply the result of building the universal secrets manager we wish had but simply didn’t exist.&lt;/p&gt;

&lt;p&gt;Doppler enables you to stop using old ways to store, share, and access secrets via .env files.&lt;/p&gt;

&lt;p&gt;It's your centralized source of truth for managing secrets across multiple clouds, platforms, and packaging formats, from cloud-native build packs to containers, serverless, and more.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dashboard.doppler.com/register?utm_source=dev&amp;amp;utm_medium=blog&amp;amp;utm_content=why-secrets-management-is-not-just-a-key-value-store-27i4"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VldNMgYL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lcu1dmvld1wzmz3b0wl0.png" alt="Create your free Doppler account" width="880" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>infrastructure</category>
    </item>
  </channel>
</rss>
