<?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: Daniel Schroeder</title>
    <description>The latest articles on Forem by Daniel Schroeder (@udondan).</description>
    <link>https://forem.com/udondan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F258664%2F568ad13d-ecf7-45f9-a916-4c81c1954b19.jpeg</url>
      <title>Forem: Daniel Schroeder</title>
      <link>https://forem.com/udondan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/udondan"/>
    <language>en</language>
    <item>
      <title>Running Docker MCP Gateway on Linux (Without Docker Desktop)</title>
      <dc:creator>Daniel Schroeder</dc:creator>
      <pubDate>Thu, 09 Apr 2026 18:46:30 +0000</pubDate>
      <link>https://forem.com/udondan/running-docker-mcp-gateway-on-linux-without-docker-desktop-4da2</link>
      <guid>https://forem.com/udondan/running-docker-mcp-gateway-on-linux-without-docker-desktop-4da2</guid>
      <description>&lt;p&gt;Docker's MCP Toolkit is a great way to expose Model Context Protocol servers to AI clients like Claude, n8n, or Cursor. Out of the box it's designed for Docker Desktop on macOS and Windows — but what if you want to run it on a headless Linux server? A Raspberry Pi, a VPS, a home lab box?&lt;/p&gt;

&lt;p&gt;This guide walks through setting it up from scratch on Linux (Debian/Ubuntu, arm64 or amd64), including secrets management, custom MCP server images, and a systemd service that starts automatically on boot.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This guide is based on getting it actually working on a Raspberry Pi 5 running&lt;br&gt;
Debian 13. Several things that look like they should work on Linux don't — I'll&lt;br&gt;
call those out explicitly so you don't waste time on the same dead ends.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;A self-hosted MCP gateway that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs any MCP server as a Docker container&lt;/li&gt;
&lt;li&gt;Exposes them all behind a single HTTP endpoint with Bearer token auth&lt;/li&gt;
&lt;li&gt;Starts automatically on boot via systemd
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Client  →  http://your-server:8811/sse  →  docker-mcp gateway  →  MCP containers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;ul&gt;
&lt;li&gt;Linux host with Docker installed (Docker Engine, not Docker Desktop)&lt;/li&gt;
&lt;li&gt;Your user in the &lt;code&gt;docker&lt;/code&gt; group (&lt;code&gt;sudo usermod -aG docker $USER&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;SSH access if setting up remotely&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1 — Install the docker-mcp Binary
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;docker mcp&lt;/code&gt; command is a Docker CLI plugin. On Linux you install it directly from the GitHub releases — no Docker Desktop needed.&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;# Create the CLI plugins directory&lt;/span&gt;
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /usr/local/lib/docker/cli-plugins

&lt;span class="c"&gt;# Download the binary for your architecture&lt;/span&gt;
&lt;span class="c"&gt;# arm64 (Raspberry Pi 4/5, Apple Silicon VMs):&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/docker/mcp-gateway/releases/download/v0.41.0/docker-mcp-linux-arm64.tar.gz &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sudo tar&lt;/span&gt; &lt;span class="nt"&gt;-xz&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; /usr/local/lib/docker/cli-plugins/

&lt;span class="c"&gt;# amd64 (regular x86 server):&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/docker/mcp-gateway/releases/download/v0.41.0/docker-mcp-linux-amd64.tar.gz &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sudo tar&lt;/span&gt; &lt;span class="nt"&gt;-xz&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; /usr/local/lib/docker/cli-plugins/

&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/lib/docker/cli-plugins/docker-mcp

&lt;span class="c"&gt;# Verify&lt;/span&gt;
docker mcp &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the &lt;a href="https://github.com/docker/mcp-gateway/releases" rel="noopener noreferrer"&gt;releases page&lt;/a&gt; for the latest version.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 — The docker-pass Workaround
&lt;/h2&gt;

&lt;p&gt;This is the first Linux-specific gotcha. &lt;code&gt;docker mcp&lt;/code&gt; CLI commands (like &lt;code&gt;docker mcp server ls&lt;/code&gt;) expect a Docker CLI plugin named &lt;code&gt;docker-pass&lt;/code&gt;. This binary ships with Docker Desktop on macOS but not on Linux, causing this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker pass has not been installed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix: a small wrapper script that satisfies the Docker CLI plugin protocol and delegates to &lt;code&gt;docker-credential-pass&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, install &lt;code&gt;docker-credential-pass&lt;/code&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;# arm64:&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/docker/docker-credential-helpers/releases/download/v0.9.5/docker-credential-pass-v0.9.5.linux-arm64 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/local/bin/docker-credential-pass

&lt;span class="c"&gt;# amd64:&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/docker/docker-credential-helpers/releases/download/v0.9.5/docker-credential-pass-v0.9.5.linux-amd64 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/local/bin/docker-credential-pass

&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/bin/docker-credential-pass
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create the wrapper plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo tee&lt;/span&gt; /usr/local/lib/docker/cli-plugins/docker-pass &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/bin/bash
if [[ "&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="sh"&gt;" == "docker-cli-plugin-metadata" ]]; then
  echo '{"SchemaVersion":"0.1.0","Vendor":"Docker","Version":"v1.0.0","ShortDescription":"Docker Pass secrets helper"}'
  exit 0
fi
exec docker-credential-pass "&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="sh"&gt;"
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/lib/docker/cli-plugins/docker-pass
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify Docker recognizes it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker info &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.ClientInfo.Plugins}}'&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;','&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;pass
&lt;span class="c"&gt;# Should show: ...pass /usr/local/lib/docker/cli-plugins/docker-pass...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This wrapper is only needed for &lt;code&gt;docker mcp&lt;/code&gt; CLI commands. The gateway&lt;br&gt;
itself uses a different mechanism for secrets — covered in Step 5.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 3 — Pull or Load Your MCP Images
&lt;/h2&gt;

&lt;p&gt;Any Docker image that implements the MCP stdio protocol can be used as an MCP server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option A: Images from the official Docker MCP catalog
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull mcp/playwright:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option B: Custom/private images
&lt;/h3&gt;

&lt;p&gt;Transfer from another 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="c"&gt;# On the source machine:&lt;/span&gt;
docker save mcp/my-server:latest | ssh your-linux-host &lt;span class="s2"&gt;"docker load"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4 — Configure the Gateway
&lt;/h2&gt;

&lt;p&gt;Create the config directory:&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;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.docker/mcp/catalogs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  registry.yaml — which servers to enable
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/.docker/mcp/registry.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
registry:
  playwright:
    ref: ""
  my-server:
    ref: ""
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  catalog.json — where to find catalog definitions
&lt;/h3&gt;

&lt;p&gt;The gateway needs to know where your catalog files live. By default it only reads the official Docker MCP catalog. Register additional catalogs here:&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/.docker/mcp/catalog.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "catalogs": {
    "docker-mcp": {
      "displayName": "Docker MCP Catalog",
      "url": "https://desktop.docker.com/mcp/catalog/v2/catalog.yaml"
    },
    "my-catalog": {
      "displayName": "My Custom Servers",
      "url": "/home/youruser/.docker/mcp/catalogs/my-catalog.yaml"
    }
  }
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A catalog YAML for a custom server
&lt;/h3&gt;

&lt;p&gt;The catalog defines how a server runs and which env vars it needs. List &lt;strong&gt;all&lt;/strong&gt; env vars under &lt;code&gt;secrets:&lt;/code&gt; — including non-sensitive ones like usernames.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Linux gotcha&lt;/strong&gt;: The &lt;code&gt;config:&lt;/code&gt; field in catalog YAMLs is not used for env var&lt;br&gt;
injection on Linux. Everything must be in &lt;code&gt;secrets:&lt;/code&gt; to be passed to containers.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;my-server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;My MCP Server&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Does something useful&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;mcp/my-server:latest&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;server&lt;/span&gt;
    &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
    &lt;span class="na"&gt;secrets&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;my-server.api_key&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;API_KEY&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;API key for the service&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-server.username&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;USERNAME&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Your username&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-catalog&lt;/span&gt;
&lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;My Catalog&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;This is the second major Linux gotcha. &lt;code&gt;docker mcp&lt;/code&gt; uses a secrets engine (&lt;code&gt;se://&lt;/code&gt; URIs) that is Docker Desktop-only and doesn't work on Linux. The &lt;code&gt;docker mcp secret set&lt;/code&gt; command will fail with &lt;code&gt;docker pass has not been installed&lt;/code&gt; even after you install the wrapper from Step 2.&lt;/p&gt;

&lt;p&gt;The solution is the &lt;code&gt;--secrets&lt;/code&gt; flag, which points the gateway at a plain 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;# Create the secrets file&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/.docker/mcp/secrets.env &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
my-server.api_key=your-api-key-here
my-server.username=your-username
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# Restrict permissions — only your user can read it&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/.docker/mcp/secrets.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key names map to the &lt;code&gt;name&lt;/code&gt; field in the catalog's &lt;code&gt;secrets:&lt;/code&gt; list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this secure?&lt;/strong&gt; The file is &lt;code&gt;chmod 600&lt;/code&gt; — readable only by your user, same as &lt;code&gt;~/.ssh/id_rsa&lt;/code&gt;. Anyone who can read it already has root or is you. If you want GPG encryption at rest, you can store sensitive values in &lt;code&gt;pass&lt;/code&gt; and populate the file from it — but for an unattended service the threat model is the same either way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are secrets isolated between MCP servers?&lt;/strong&gt; Yes, completely. The secrets file is never passed to or mounted into any container. The gateway reads it internally and uses it purely as a lookup table. When spawning each container it passes only the specific &lt;code&gt;-e VAR=value&lt;/code&gt; flags declared in that server's catalog &lt;code&gt;secrets:&lt;/code&gt; list. You can verify this with &lt;code&gt;--dry-run --verbose&lt;/code&gt; — the &lt;code&gt;docker run&lt;/code&gt; command for each server is logged in full, and you'll see that playwright gets zero secret env vars, while my-server only gets &lt;code&gt;USERNAME&lt;/code&gt; and &lt;code&gt;API_KEY&lt;/code&gt;. There is no way for one MCP server to access another's credentials.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6 — Test the Gateway
&lt;/h2&gt;

&lt;p&gt;Do a dry run first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker mcp gateway run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dry-run&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--verbose&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secrets&lt;/span&gt; ~/.docker/mcp/secrets.env &lt;span class="se"&gt;\&lt;/span&gt;
  2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see all your configured servers listed and their tools counted, with no &lt;code&gt;Warning: Secret '...' not found&lt;/code&gt; lines. If warnings appear, check that the key names in &lt;code&gt;secrets.env&lt;/code&gt; exactly match the &lt;code&gt;name&lt;/code&gt; fields in your catalog YAML.&lt;/p&gt;

&lt;p&gt;If everything looks good, start it live:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker mcp gateway run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transport&lt;/span&gt; sse &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; 8811 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secrets&lt;/span&gt; ~/.docker/mcp/secrets.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 7 — systemd Service
&lt;/h2&gt;

&lt;p&gt;Create the service 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="nb"&gt;sudo tee&lt;/span&gt; /etc/systemd/system/mcp-gateway.service &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
[Unit]
Description=Docker MCP Gateway
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=simple
User=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;whoami&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
Environment=HOME=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="sh"&gt;
ExecStart=/usr/local/lib/docker/cli-plugins/docker-mcp gateway run &lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;
  --transport sse &lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;
  --port 8811 &lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;
  --secrets &lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="sh"&gt;/.docker/mcp/secrets.env
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set a stable Bearer token that survives restarts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /etc/systemd/system/mcp-gateway.service.d

&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Save this token: &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/systemd/system/mcp-gateway.service.d/token.conf &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
[Service]
Environment=MCP_GATEWAY_AUTH_TOKEN=&lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="sh"&gt;
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, the gateway generates a new random token on every start — which means reconfiguring every client after each restart.&lt;/p&gt;

&lt;p&gt;Enable and start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;mcp-gateway.service
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start mcp-gateway.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check it's running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status mcp-gateway.service
journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; mcp-gateway.service &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Connecting a Client
&lt;/h2&gt;

&lt;p&gt;The gateway runs on port &lt;code&gt;8811&lt;/code&gt; with SSE transport:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;URL:   http://your-server:8811/sse
Auth:  Authorization: Bearer &amp;lt;your-token&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Claude Desktop
&lt;/h3&gt;

&lt;p&gt;Add to &lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt;&lt;br&gt;
(macOS) or the equivalent on your OS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"my-gateway"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"mcp-remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"http://your-server:8811/sse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--header"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Authorization: Bearer &amp;lt;your-token&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--allow-http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--transport"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"sse-only"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two flags are required here that aren't obvious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--allow-http&lt;/code&gt; — &lt;code&gt;mcp-remote&lt;/code&gt; blocks non-HTTPS URLs by default&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--transport sse-only&lt;/code&gt; — the default &lt;code&gt;http-first&lt;/code&gt; strategy sends a POST that
the gateway rejects with &lt;code&gt;sessionid must be provided&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;docker pass has not been installed&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;docker-pass&lt;/code&gt; CLI plugin wrapper is missing or not executable. Re-check Step 2.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;Warning: Secret '...' not found&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The key name in &lt;code&gt;secrets.env&lt;/code&gt; doesn't match the &lt;code&gt;name&lt;/code&gt; field in the catalog YAML, or you forgot to pass &lt;code&gt;--secrets&lt;/code&gt; to the gateway. Check with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker mcp gateway run &lt;span class="nt"&gt;--dry-run&lt;/span&gt; &lt;span class="nt"&gt;--verbose&lt;/span&gt; &lt;span class="nt"&gt;--secrets&lt;/span&gt; ~/.docker/mcp/secrets.env 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;grep &lt;/span&gt;Warning
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Server shows 0 tools in dry-run but works live
&lt;/h3&gt;

&lt;p&gt;Some servers need the actual secrets present to respond to tool listing. This is normal — the gateway still starts them correctly at runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;cannot use --port with --transport=stdio&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;You must specify &lt;code&gt;--transport sse&lt;/code&gt; when using &lt;code&gt;--port&lt;/code&gt;. The default transport is stdio (for direct client connections), not HTTP.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;sessionid must be provided&lt;/code&gt; in mcp-remote
&lt;/h3&gt;

&lt;p&gt;Add &lt;code&gt;--transport sse-only&lt;/code&gt; to the &lt;code&gt;mcp-remote&lt;/code&gt; args. The default transport strategy tries Streamable HTTP first, which the gateway doesn't support.&lt;/p&gt;




&lt;h2&gt;
  
  
  Keeping Things Updated
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Upgrade docker-mcp
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v0.41.0  &lt;span class="c"&gt;# replace with latest&lt;/span&gt;
&lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arm64       &lt;span class="c"&gt;# or amd64&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/docker/mcp-gateway/releases/download/&lt;span class="nv"&gt;$VERSION&lt;/span&gt;/docker-mcp-linux-&lt;span class="nv"&gt;$ARCH&lt;/span&gt;.tar.gz &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sudo tar&lt;/span&gt; &lt;span class="nt"&gt;-xz&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; /usr/local/lib/docker/cli-plugins/

&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/lib/docker/cli-plugins/docker-mcp
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart mcp-gateway.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update a custom MCP image
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker save mcp/my-server:latest | ssh your-linux-host &lt;span class="s2"&gt;"docker load"&lt;/span&gt;
ssh your-linux-host &lt;span class="s2"&gt;"sudo systemctl restart mcp-gateway.service"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;The key differences from macOS Docker Desktop:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;macOS (Docker Desktop)&lt;/th&gt;
&lt;th&gt;Linux (headless)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;docker-mcp&lt;/code&gt; binary&lt;/td&gt;
&lt;td&gt;Bundled with Docker Desktop&lt;/td&gt;
&lt;td&gt;Downloaded from GitHub releases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;docker-pass&lt;/code&gt; plugin&lt;/td&gt;
&lt;td&gt;Proprietary binary&lt;/td&gt;
&lt;td&gt;Wrapper script → &lt;code&gt;docker-credential-pass&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets injection&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker mcp secret set&lt;/code&gt; + keychain&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--secrets &amp;lt;env-file&amp;gt;&lt;/code&gt; (chmod 600)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-start&lt;/td&gt;
&lt;td&gt;Docker Desktop&lt;/td&gt;
&lt;td&gt;systemd service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transport&lt;/td&gt;
&lt;td&gt;stdio or SSE&lt;/td&gt;
&lt;td&gt;SSE with &lt;code&gt;--transport sse&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mcp-remote&lt;/td&gt;
&lt;td&gt;Default settings work&lt;/td&gt;
&lt;td&gt;Needs &lt;code&gt;--allow-http --transport sse-only&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Everything else — catalog YAMLs, registry.yaml, the &lt;code&gt;docker mcp&lt;/code&gt; CLI — works identically between macOS and Linux once the above pieces are in place.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>linux</category>
      <category>mcp</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Correctly defining CDK dependencies in L3 constructs</title>
      <dc:creator>Daniel Schroeder</dc:creator>
      <pubDate>Mon, 28 Dec 2020 21:25:17 +0000</pubDate>
      <link>https://forem.com/aws-builders/correctly-defining-dependencies-in-l3-cdk-constructs-45p</link>
      <guid>https://forem.com/aws-builders/correctly-defining-dependencies-in-l3-cdk-constructs-45p</guid>
      <description>&lt;p&gt;When creating L3 constructs for the &lt;a href="https://aws.amazon.com/cdk/" rel="noopener noreferrer"&gt;AWS CDK&lt;/a&gt;, the easiest thing to get wrong is defining dependencies. And with wrong dependency definitions you make it hard to impossible to use your package. This post will show how to correctly define CDK dependencies.&lt;/p&gt;

&lt;p&gt;As a CDK user, you already know, all CDK core packages have to be of the same version or you will get cryptic errors such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unable to determine cloud assembly output directory. Assets must be defined indirectly within a "Stage" or an "App" scope
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Types of property 'node' are incompatible... Types have separate declarations of a private property 'host'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So what you want to avoid when publishing L3 constructs, is to directly depend your package on any version of the core CDK packages. Still, this is the case in almost every L3 construct I have seen. I'm assuming this is because the core packages themselves do it like this and developers learn from reading code.&lt;/p&gt;

&lt;p&gt;Usually you find packages having either exact or caret versions in their &lt;code&gt;dependencies&lt;/code&gt;. Also, in most cases, these definitions are accompanied with the same items in the &lt;code&gt;peerDependencies&lt;/code&gt;. Let's analyze these two setups and what the result for the end-user is going to be:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependencies with caret version&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@aws-cdk/aws-lambda"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.23.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"peerDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@aws-cdk/aws-lambda"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.23.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The caret definition means, &lt;em&gt;install the latest minor compatible to the given version&lt;/em&gt;. That is everything &amp;lt; 2.0.0. So until CDK 2 drops, this will install the very latest package for &lt;code&gt;aws-lambda&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The end-user now can only install your package, when all application dependencies refer to the latest CDK packages as well. If CDK 1.70.0 or any other older version is used, there is no way the user can install your package without causing incompatibility between core packages. The solution from user perspective is to upgrade the CDK and deal with the potentially introduced breaking changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependencies with exact version&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@aws-cdk/aws-lambda"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.80.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"peerDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@aws-cdk/aws-lambda"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.80.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A package with this &lt;code&gt;dependencies&lt;/code&gt; definition of course is only compatible with exactly one version of the CDK. This means your users cannot upgrade the CDK without upgrading your package and vice versa. To make your package usable by future CDK versions you need to release new versions of your package for every CDK release. Most probably you do this automated. And either you have a mapping between CDK version and your package version in your docs or you use the same exact version as the upstream packages, which renders the information (&lt;a href="https://semver.org" rel="noopener noreferrer"&gt;semver&lt;/a&gt;) of &lt;em&gt;your&lt;/em&gt; version string useless. How do you progress your own code? How do you communicate bug-fixes or breaking changes? And let's not forget, you waste computational resources for compiling, transferring and storing your build artifacts without actual change.&lt;/p&gt;

&lt;p&gt;The problem though, is not the format of your dependency definition (exact vs. caret) - the problem simply is: &lt;strong&gt;You have dependencies&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And as simple as that problem statement, is the solution: &lt;strong&gt;Just don't&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd5jxy1yo7mu87tzyylu9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd5jxy1yo7mu87tzyylu9.png" alt="Thou shalt not list CDK core packages in thy dependencies"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead, list them in the &lt;code&gt;peerDependencies&lt;/code&gt; and &lt;code&gt;devDependencies&lt;/code&gt;. You should use caret versions, defining the minimum version &lt;strong&gt;required by your package&lt;/strong&gt;. Typically this should be the version when all your CDK dependencies went stable. If you don't know or don't care, use &lt;code&gt;^1.0.0&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@aws-cdk/aws-lambda"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.0.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"peerDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@aws-cdk/aws-lambda"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.0.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need them in the &lt;code&gt;devDependencies&lt;/code&gt; to build your package with &lt;a href="https://aws.github.io/jsii/" rel="noopener noreferrer"&gt;jsii&lt;/a&gt; and you need them in the &lt;code&gt;peerDependencies&lt;/code&gt; to let the user know what packages need to be installed along with your package.&lt;/p&gt;

&lt;p&gt;Now, when a user installs your package, what happens depends on the used language and package manager. Either way, the user needs to define the peer-dependencies as dependencies of the application, e.g. in the &lt;code&gt;package.json&lt;/code&gt; or &lt;code&gt;requirements.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;npm version &amp;lt; 6&lt;/strong&gt; will warn about missing peer dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm WARN cdk-awesome-package@1.2.3 requires a peer of @aws-cdk/aws-lambda@^1.0.0 but none was installed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same goes for &lt;strong&gt;yarn&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;warning " &amp;gt; cdk-awesome-package@1.2.3" has unmet peer dependency "@aws-cdk/aws-lambda@^1.0.0".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;strong&gt;npm 6&lt;/strong&gt; for some reason this is missing and the user will only know about the missing dependency when the application is ran. That's fine though, the error message is clear about what package needs to be installed. An entry in the &lt;code&gt;package.json&lt;/code&gt; needs to be made:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@aws-cdk/aws-lambda"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.70.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cdk-awesome-package"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.2.3"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Starting with &lt;strong&gt;npm version 7&lt;/strong&gt; the handling of peer dependencies has changed - they will be automatically installed and you cannot override the version in the &lt;code&gt;dependencies&lt;/code&gt; section. Instead the user now needs to add it to the &lt;code&gt;overrides&lt;/code&gt; section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cdk-awesome-package"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.2.3"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"overrides"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@aws-cdk/aws-lambda"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.70.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively the user can also just switch back to the old behaviour by setting &lt;code&gt;legacy-peer-deps=true&lt;/code&gt; in the &lt;code&gt;.npmrc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All other languages supported by jsii don't support peer dependencies. For these languages they are converted to normal dependencies. pip correctly interprets the caret version definition of &lt;code&gt;^1.0.0&lt;/code&gt; and in return you would install the latest version. For the dotnet package, jsii converted &lt;code&gt;^1.0.0&lt;/code&gt; to &lt;code&gt;1.0.0&lt;/code&gt;...  which in the end really doesn't matter. Because this can and has to be overridden by the user anyway.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;pip&lt;/strong&gt; the user can override the version of dependencies by simply adding them to the applications &lt;code&gt;requirements.txt&lt;/code&gt; or &lt;code&gt;setup.py&lt;/code&gt;, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws-cdk-aws-lambda==1.70.0
cdk-awesome-package&amp;gt;=1.2.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In C# the user can also override the version in the project file, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Amazon.CDK.AWS.LAMBDA"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.70.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"CDK.Awesome.Package"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.*"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I haven't checked Java but I assume the same can be archived in a gradle file. &lt;/p&gt;

&lt;p&gt;And there you go. A CDK L3 construct working with any version of core CDK packages.&lt;/p&gt;




&lt;h4&gt;
  
  
  Update 2021/04/17
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;But what about breaking changes?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I've seen some developers are automatically releasing new versions of their L3 constructs for every new CDK release for the reason of compatibility, especially when experimental CDK components are involved. I strongly disagree with this approach.&lt;/p&gt;

&lt;p&gt;First of all, if an L3 uses experimental features, then the L3, as a consequence, is experimental. It should be used with caution and breaking changes should be expected with every minor update. Most likely it also should not be used in production, unless the user knows what she/he's doing and pays the proper attention. Using experimental features means, the user might not be able to upgrade without destroying/recreating related infrastructure.&lt;/p&gt;

&lt;p&gt;Next: Ensuring compatibility with future versions of a framework is not the responsibility of a package author. If there are breaking changes, of course things will fail. The same would be true if the user directly used experimental CDK features. The responsibility of catching these is not with the L3 author, but with the user of the construct. Every user needs to be aware that the smallest change needs testing. Of course, when there are package changes, the app needs to be deployed in a test environment, ran with &lt;code&gt;diff&lt;/code&gt; and ideally even has proper jest tests in place, before it reaches your prod environment.&lt;/p&gt;

&lt;p&gt;You cannot solve testing for the user. The best you can catch by building a package per CDK version is a change in the API signature. Maybe an option has been removed or renamed and therefore your code is not compatible and won't compile. The real danger though lies in functional changes, that make the code run, but behave differently. A very good example for this would be &lt;a href="https://github.com/aws/aws-cdk/releases/tag/v1.75.0" rel="noopener noreferrer"&gt;CDK version 1.75.0&lt;/a&gt;, which included the following change:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;efs: &lt;code&gt;keyId&lt;/code&gt; property uses the ARN instead of the &lt;code&gt;keyId&lt;/code&gt; to support cross-account encryption key usage. &lt;strong&gt;The filesystem will be replaced.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;This&lt;/strong&gt; is the real danger with experimental packages and you cannot protect the user from this by building a new version of your package.&lt;/p&gt;




&lt;p&gt;Also, what's exactly the benefit of building new packages for every release? You ensure it still builds. So what if it doesn't? Your build fails, you'll be notified by your action/workflow and you need to adjust the code to match the new API, because building new packages does not automagically fix the problem. Let's assume you're knee-deep involved in other projects or actual life and you don't have time to fix it for some time. &lt;br&gt;
&lt;strong&gt;Consequence: No user will be able to upgrade the core CDK packages until you fixed your code and released a compatible package.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now imagine you had not auto-built new packages but instead used my suggested best practice? The package will not be compatible with the latest CDK version. &lt;br&gt;
&lt;strong&gt;Consequence: No user will be able to upgrade the core CDK packages until you fixed your code and released a compatible package.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The only difference would be, that you won't be notified by your failed pipeline/action and wouldn't be aware there is a breaking change. And this can easily be fixed by testing your package for new CDK releases.&lt;/p&gt;

&lt;p&gt;So instead of building and publishing new packages for absolutely no reason, you should &lt;strong&gt;test&lt;/strong&gt; your package against every new CDK release.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What about &lt;a href="https://github.com/projen/projen" rel="noopener noreferrer"&gt;projen&lt;/a&gt;?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/projen/projen/pull/581" rel="noopener noreferrer"&gt;Since 1st of March 2021&lt;/a&gt; you can set &lt;code&gt;cdkDependenciesAsDeps: false&lt;/code&gt; in your &lt;code&gt;.projenrc.js&lt;/code&gt; to prevent projen from adding the CDK packages as &lt;code&gt;dependencies&lt;/code&gt; and only list them as &lt;code&gt;peerDependencies&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cdk</category>
      <category>dependency</category>
    </item>
    <item>
      <title>Terraform workspace config organization</title>
      <dc:creator>Daniel Schroeder</dc:creator>
      <pubDate>Wed, 20 Nov 2019 15:23:55 +0000</pubDate>
      <link>https://forem.com/udondan/terraform-workspace-config-organization-42lh</link>
      <guid>https://forem.com/udondan/terraform-workspace-config-organization-42lh</guid>
      <description>&lt;p&gt;The suggested best practices for organizing configuration for multiple workspaces/environments is to call Terraform with &lt;code&gt;-var-file=$env&lt;/code&gt; to include a specific tfvars file.&lt;/p&gt;

&lt;p&gt;Of course this works. But it seems to be error prone if you allow to trigger an apply against any workspace with just any configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform workspace &lt;span class="k"&gt;select &lt;/span&gt;production
terraform apply &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;config/staging.tfvars
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Furthermore this cannot be used in Terraform Cloud, where you have to specify workspace related vars in the workspace configuration itself:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oMHXPiXT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/s1trk20rgji6tfgyldv5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oMHXPiXT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/s1trk20rgji6tfgyldv5.png" alt="Terraform Cloud Variable configuration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is no option for including tfvars per workspace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Select config automatically based on the workspace
&lt;/h3&gt;

&lt;p&gt;Unfortunately there is no functionality to &lt;a href="https://github.com/hashicorp/terraform/issues/15966"&gt;automatically include a tfvars file based on the workspace name&lt;/a&gt; nor is there support for &lt;a href="https://github.com/hashicorp/terraform/issues/1478"&gt;conditionally including tfvars files&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So you got to build something yourself. You can access the current workspace name via &lt;code&gt;terraform.workspace&lt;/code&gt;. There are a couple of things you can do with this value.&lt;/p&gt;

&lt;p&gt;Below are 3 solutions which all have the same exact outcome. The defined config is stored in &lt;code&gt;local.config&lt;/code&gt; and can be access via &lt;code&gt;local.config.ec2_instance_type&lt;/code&gt; etc.&lt;/p&gt;

&lt;p&gt;All 3 solutions support default values, so you're not required to define every config option in every environment.&lt;/p&gt;

&lt;p&gt;All examples are available in &lt;a href="https://github.com/udondan/example-terraform-workspace-config"&gt;this repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Inline expressions to select correct config from a map
&lt;/h3&gt;

&lt;p&gt;All config is held is a single file &lt;a href="https://github.com/udondan/example-terraform-workspace-config/blob/master/inline-locals/config.tf"&gt;&lt;code&gt;config.tf&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;configs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nx"&gt;_defaults&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ec2_instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.nano"&lt;/span&gt;
      &lt;span class="nx"&gt;regions&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;dev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="c1"&gt;// use config from _defaults ^&lt;/span&gt;

    &lt;span class="nx"&gt;staging&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ec2_instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.medium"&lt;/span&gt;
      &lt;span class="nx"&gt;regions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"eu-central-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;production&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ec2_instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.xlarge"&lt;/span&gt;
      &lt;span class="nx"&gt;regions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"us-west-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"eu-central-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"ap-east-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_defaults"&lt;/span&gt;&lt;span class="err"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;terraform&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspace&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="err"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Straight forward&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Cannot split config into separate files. Therefore could quickly get hard to maintain and compare environment config.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Load config from YAML files
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/udondan/example-terraform-workspace-config/tree/master/include-per-yaml-file"&gt;Directory structure:&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── config
│   ├── _defaults.yml
│   ├── dev.yml
│   ├── production.yml
│   └── staging.yml
├── config.tf
├── main.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example content of &lt;a href="https://github.com/udondan/example-terraform-workspace-config/blob/master/include-per-yaml-file/config/production.yml"&gt;&lt;code&gt;config/production.yml&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;ec2_instance_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;t2.xlarge&lt;/span&gt;

&lt;span class="na"&gt;regions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;us-west-2&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;eu-central-1&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ap-east-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The config is loaded in &lt;a href="https://github.com/udondan/example-terraform-workspace-config/blob/master/include-per-yaml-file/config.tf"&gt;&lt;code&gt;config.yml&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"local_file"&lt;/span&gt; &lt;span class="s2"&gt;"defaults"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/config/_defaults.yml"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"local_file"&lt;/span&gt; &lt;span class="s2"&gt;"config"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/config/${terraform.workspace}.yml"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;yamldecode&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local_file&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaults&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="err"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;yamldecode&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local_file&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="err"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Straight forward&lt;/li&gt;
&lt;li&gt;Config for every environment resides in its own file&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;No HCL expressions are possible in the config itself&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Create a module per environment and return the config as an output
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/udondan/example-terraform-workspace-config/tree/master/include-per-module"&gt;Directory structure:&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── config
│   ├── _defaults
│   │   └── outputs.tf
│   ├── dev
│   │   └── outputs.tf
│   ├── main.tf
│   ├── production
│   │   └── outputs.tf
│   └── staging
│       └── outputs.tf
├── main.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In every &lt;code&gt;config/$env/outputs.tf&lt;/code&gt; a single output is defined like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/udondan/example-terraform-workspace-config/blob/master/include-per-module/config/_defaults/outputs.tf"&gt;&lt;code&gt;config/_defaults/outputs.tf&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"data"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ec2_instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.nano"&lt;/span&gt;
    &lt;span class="nx"&gt;regions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/udondan/example-terraform-workspace-config/blob/master/include-per-module/config/dev/outputs.tf"&gt;config/dev/outputs.tf&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"data"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="c1"&gt;// use config from _defaults&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/udondan/example-terraform-workspace-config/blob/master/include-per-module/config/staging/outputs.tf"&gt;config/staging/outputs.tf&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"data"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ec2_instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.medium"&lt;/span&gt;
    &lt;span class="nx"&gt;regions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"eu-central-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/udondan/example-terraform-workspace-config/blob/master/include-per-module/config/production/outputs.tf"&gt;config/production/outputs.tf&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"data"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ec2_instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.xlarge"&lt;/span&gt;
    &lt;span class="nx"&gt;regions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"us-west-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"eu-central-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"ap-east-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since you cannot use variables in a module &lt;code&gt;source&lt;/code&gt; parameter all 4 modules have to be defined in every environment. Furthermore you cannot directly access a module by name when the name is not hardcoded, so you need to additionally create a mapping like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/udondan/example-terraform-workspace-config/blob/master/include-per-module/config/main.tf"&gt;config/main.tf&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"_defaults"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./_defaults"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./dev"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"staging"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./staging"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"production"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./production"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;data_map&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;dev&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;staging&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;staging&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;production&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;production&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"data"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_defaults&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data_map&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;terraform&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspace&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="err"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;a href="https://github.com/udondan/example-terraform-workspace-config/blob/master/include-per-module/main.tf"&gt;&lt;code&gt;main.tf&lt;/code&gt;&lt;/a&gt; then the module needs to be loaded and for convenience the output gets registered as a local value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"config"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./config"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Config for every environment resides in its own file&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Complex setup&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Using modules as config provider seems to be the best solution, as you can split the configuration into separate files which supports HCL expressions. The setup though is complex and requires some additional boilerplate code for every additional environment.&lt;/p&gt;

&lt;p&gt;If you have no need for HCL expressions, the YAML solution seems to be nice as it is easy to setup and IMHO is very readable to humans.&lt;/p&gt;

</description>
      <category>terraform</category>
    </item>
  </channel>
</rss>
