<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Twelve-Factor on Tarragon</title><link>https://tarrragon.github.io/blog/tags/twelve-factor/</link><description>Recent content in Twelve-Factor on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Mon, 06 Jul 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/twelve-factor/index.xml" rel="self" type="application/rss+xml"/><item><title>十二要素基線</title><link>https://tarrragon.github.io/blog/going-live/twelve-factor-baseline/</link><pubDate>Mon, 06 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/going-live/twelve-factor-baseline/</guid><description>&lt;p>十二要素（Twelve-Factor App）是一套「讓服務好部署、好搬家、好擴展」的基本紀律。它原本是 12 條，但對第一次上線的人，真正天天影響你的是其中幾條——它們的共通精神是&lt;strong>把「會變的東西」跟「code」分開&lt;/strong>，這樣同一份 code 才能在你的筆電、staging（上線前的測試環境）、prod 三個環境不改一行就跑起來。&lt;/p>
&lt;h2 id="設定進環境變數不寫死在-code-裡">設定進環境變數，不寫死在 code 裡&lt;/h2>
&lt;p>這是最重要的一條。資料庫位址、API 金鑰、外部服務 URL——這些&lt;strong>每個環境都不一樣&lt;/strong>的東西，不能寫死在 code 裡，要從&lt;strong>環境變數&lt;/strong>讀：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl"># 不要這樣（寫死，換環境要改 code）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">db = connect(&amp;#34;mysql://root:secret@localhost/app&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"># 要這樣（從環境變數讀，換環境只換變數）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">db = connect(env(&amp;#34;DATABASE_URL&amp;#34;))&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>好處：同一份 code 在本機讀到本機的 DB、在 prod 讀到 prod 的 DB，不用改 code、也不會把 prod 密鑰簽進 git。密鑰怎麼安全注入見 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">Backend secret 治理&lt;/a>。&lt;/p>
&lt;h2 id="process-無狀態資料放外面">process 無狀態，資料放外面&lt;/h2>
&lt;p>你的 app process 本身&lt;strong>不存資料&lt;/strong>——使用者 session、上傳的檔案、快取，都放到外部的後端服務（DB、Redis、物件儲存），不放在 app 的記憶體或本機磁碟。&lt;/p>
&lt;p>為什麼：這樣你才能隨時砍掉一個 app、重開一個、或同時開三個副本擋流量——因為資料不在 app 裡，砍了不會掉東西。這條是水平擴展的前提，深入見 &lt;a href="https://tarrragon.github.io/blog/operations/02-horizontal-scaling/stateless-design/" data-link-title="Stateless 設計原則" data-link-desc="要把服務改成能水平擴展的無狀態設計時，釐清什麼算「本機狀態」、哪些常見寫法會破壞無狀態、隱式狀態怎麼抓、以及定時 job 這種無狀態例外怎麼處理">運行期維運 無狀態設計&lt;/a>。&lt;/p>
&lt;h2 id="log-輸出到-stdout當成串流">log 輸出到 stdout，當成串流&lt;/h2>
&lt;p>不要讓 app 自己寫 log 檔、自己輪替。把 log 直接印到標準輸出（stdout），交給外面的平台去收集、儲存、查詢。這樣不管 app 跑在哪、幾個副本，log 都被統一收走，你不用去每台機器翻檔案。&lt;/p>
&lt;h2 id="明確宣告依賴開機即用">明確宣告依賴、開機即用&lt;/h2>
&lt;p>app 依賴哪些套件，要寫在 lockfile 裡（&lt;code>package-lock.json&lt;/code>、&lt;code>go.sum&lt;/code>、&lt;code>requirements.txt&lt;/code>），部署時照 lockfile 裝——不靠「機器上剛好有」。而且 app 要能&lt;strong>快速啟動、優雅關閉&lt;/strong>（收到停止信號先處理完手上的請求再退），這樣部署新版、自動重啟才順。&lt;/p>
&lt;h2 id="dev--prod-儘量一致">dev / prod 儘量一致&lt;/h2>
&lt;p>本機開發環境跟線上環境的差距越小，「在我電腦上能跑、上線卻壞」的機率越低。這條在你用舊版 runtime 的 client 環境時特別關鍵——本機要對齊線上的版本與行為，見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/10-prod-parity/" data-link-title="模組十：Prod Parity — 跨發行版與線上環境對齊" data-link-desc="工作站是 Arch 這種滾動最新版、但要開發的 client 線上跑的是凍結舊環境（PHP 7.2 / MySQL 5.7 / Debian）時回來讀 — dotfile 哲學怎麼跨 distro 落地、怎麼建對齊 prod 的 runtime">Dotfile 模組十：Prod Parity&lt;/a>。&lt;/p>
&lt;h2 id="判讀這些不是潔癖是省未來的痛">判讀：這些不是潔癖，是省未來的痛&lt;/h2>
&lt;p>每一條都對應一個「沒做就會在某個時刻痛」的場景：設定寫死 → 換環境或輪替密鑰時要改 code、還可能把密鑰簽進 git；process 有狀態 → 想加一台擋流量卻發現 session 掉了；log 寫檔 → 出事要 SSH 進每台機器撈。還有一條上 PaaS 當天就會撞的：&lt;strong>listen 在平台指派的 &lt;code>$PORT&lt;/code>&lt;/strong>——Heroku / Cloud Run 這類平台會給你一個環境變數指定 port，app 沒讀它、寫死自己的 port，就部署失敗。第一次上線可以先抓住「設定進環境變數」跟「process 無狀態」這兩條（上 PaaS 再加 &lt;code>$PORT&lt;/code>），其餘隨服務長大再補。&lt;/p>
&lt;h2 id="下一步">下一步&lt;/h2>
&lt;p>服務好部署了，接著常要決定「有狀態的那部分（資料庫）自己顧還是託管」——見 &lt;a href="https://tarrragon.github.io/blog/going-live/self-host-vs-managed-db/" data-link-title="自架還是託管：以資料庫為例" data-link-desc="在 VPS 上自己跑一個 DB container 很便宜、看到 RDS / Cloud SQL 一個月要幾十鎂而猶豫時回來讀 — 成本差在哪、差價其實在買什麼">自架還是託管：以資料庫為例&lt;/a>。config 注入與 runtime 設定的進階做法見 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">Backend 部署平台&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>十二要素（Twelve-Factor App）是一套「讓服務好部署、好搬家、好擴展」的基本紀律。它原本是 12 條，但對第一次上線的人，真正天天影響你的是其中幾條——它們的共通精神是<strong>把「會變的東西」跟「code」分開</strong>，這樣同一份 code 才能在你的筆電、staging（上線前的測試環境）、prod 三個環境不改一行就跑起來。</p>
<h2 id="設定進環境變數不寫死在-code-裡">設定進環境變數，不寫死在 code 裡</h2>
<p>這是最重要的一條。資料庫位址、API 金鑰、外部服務 URL——這些<strong>每個環境都不一樣</strong>的東西，不能寫死在 code 裡，要從<strong>環境變數</strong>讀：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl"># 不要這樣（寫死，換環境要改 code）
</span></span><span class="line"><span class="ln">2</span><span class="cl">db = connect(&#34;mysql://root:secret@localhost/app&#34;)
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"># 要這樣（從環境變數讀，換環境只換變數）
</span></span><span class="line"><span class="ln">5</span><span class="cl">db = connect(env(&#34;DATABASE_URL&#34;))</span></span></code></pre></div><p>好處：同一份 code 在本機讀到本機的 DB、在 prod 讀到 prod 的 DB，不用改 code、也不會把 prod 密鑰簽進 git。密鑰怎麼安全注入見 <a href="/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">Backend secret 治理</a>。</p>
<h2 id="process-無狀態資料放外面">process 無狀態，資料放外面</h2>
<p>你的 app process 本身<strong>不存資料</strong>——使用者 session、上傳的檔案、快取，都放到外部的後端服務（DB、Redis、物件儲存），不放在 app 的記憶體或本機磁碟。</p>
<p>為什麼：這樣你才能隨時砍掉一個 app、重開一個、或同時開三個副本擋流量——因為資料不在 app 裡，砍了不會掉東西。這條是水平擴展的前提，深入見 <a href="/blog/operations/02-horizontal-scaling/stateless-design/" data-link-title="Stateless 設計原則" data-link-desc="要把服務改成能水平擴展的無狀態設計時，釐清什麼算「本機狀態」、哪些常見寫法會破壞無狀態、隱式狀態怎麼抓、以及定時 job 這種無狀態例外怎麼處理">運行期維運 無狀態設計</a>。</p>
<h2 id="log-輸出到-stdout當成串流">log 輸出到 stdout，當成串流</h2>
<p>不要讓 app 自己寫 log 檔、自己輪替。把 log 直接印到標準輸出（stdout），交給外面的平台去收集、儲存、查詢。這樣不管 app 跑在哪、幾個副本，log 都被統一收走，你不用去每台機器翻檔案。</p>
<h2 id="明確宣告依賴開機即用">明確宣告依賴、開機即用</h2>
<p>app 依賴哪些套件，要寫在 lockfile 裡（<code>package-lock.json</code>、<code>go.sum</code>、<code>requirements.txt</code>），部署時照 lockfile 裝——不靠「機器上剛好有」。而且 app 要能<strong>快速啟動、優雅關閉</strong>（收到停止信號先處理完手上的請求再退），這樣部署新版、自動重啟才順。</p>
<h2 id="dev--prod-儘量一致">dev / prod 儘量一致</h2>
<p>本機開發環境跟線上環境的差距越小，「在我電腦上能跑、上線卻壞」的機率越低。這條在你用舊版 runtime 的 client 環境時特別關鍵——本機要對齊線上的版本與行為，見 <a href="/blog/linux/dotfile/10-prod-parity/" data-link-title="模組十：Prod Parity — 跨發行版與線上環境對齊" data-link-desc="工作站是 Arch 這種滾動最新版、但要開發的 client 線上跑的是凍結舊環境（PHP 7.2 / MySQL 5.7 / Debian）時回來讀 — dotfile 哲學怎麼跨 distro 落地、怎麼建對齊 prod 的 runtime">Dotfile 模組十：Prod Parity</a>。</p>
<h2 id="判讀這些不是潔癖是省未來的痛">判讀：這些不是潔癖，是省未來的痛</h2>
<p>每一條都對應一個「沒做就會在某個時刻痛」的場景：設定寫死 → 換環境或輪替密鑰時要改 code、還可能把密鑰簽進 git；process 有狀態 → 想加一台擋流量卻發現 session 掉了；log 寫檔 → 出事要 SSH 進每台機器撈。還有一條上 PaaS 當天就會撞的：<strong>listen 在平台指派的 <code>$PORT</code></strong>——Heroku / Cloud Run 這類平台會給你一個環境變數指定 port，app 沒讀它、寫死自己的 port，就部署失敗。第一次上線可以先抓住「設定進環境變數」跟「process 無狀態」這兩條（上 PaaS 再加 <code>$PORT</code>），其餘隨服務長大再補。</p>
<h2 id="下一步">下一步</h2>
<p>服務好部署了，接著常要決定「有狀態的那部分（資料庫）自己顧還是託管」——見 <a href="/blog/going-live/self-host-vs-managed-db/" data-link-title="自架還是託管：以資料庫為例" data-link-desc="在 VPS 上自己跑一個 DB container 很便宜、看到 RDS / Cloud SQL 一個月要幾十鎂而猶豫時回來讀 — 成本差在哪、差價其實在買什麼">自架還是託管：以資料庫為例</a>。config 注入與 runtime 設定的進階做法見 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">Backend 部署平台</a>。</p>
]]></content:encoded></item></channel></rss>