whoops.

YAML 教學:語法規則、挪威問題與六大應用場景一次搞懂

YAML 設定檔以縮排組成階層,並提示型別與安全載入三個閱讀重點
先讀出結構,再確認值的型別與載入方式,是讀懂 YAML 設定的三個切入點。

YAML 教學完整指南:從 YAML 是什麼、語法規則精要、挪威問題布林陷阱、多行字串與錨點合併鍵,到 Kubernetes、Docker Compose、GitHub Actions、OpenAPI 與 Astro frontmatter 六大應用場景,加上 Python safe_load 資安須知,一次學會讀寫與除錯。

本頁目錄

接手一個新專案,打開資料夾,第一份被你看到的檔案,很可能就是一份副檔名為 .yaml 的設定檔,排在程式碼前面:部署清單、CI 流程、容器編排、API 文件,全都是它。想請同事示範怎麼改,對方丟回來一句「就是縮排而已」;自己動手,卻在存檔之後收到一串看不懂的錯誤。更麻煩的是,明明只是改了一個 yes,系統的行為卻整個變了樣,找了半天才知道問題出在 YAML 對「看起來像布林的字」的處理。

這份教學把 YAML 從定義、語法規則、經典陷阱到實際應用場景一次講完,並且以YAML 1.2.2 官方規格書為主要依據。讀完之後你會知道:為什麼縮排不能用 Tab、為什麼挪威的國碼會讓設定檔出錯(也就是著名的「挪威問題」)、多行字串的豎線與折疊符號差在哪、以及同一份設定怎麼被 Kubernetes、Docker Compose、GitHub Actions 到 AI 工具重複使用。

核心重點:YAML 是一種給人讀、也給機器讀的資料格式,用縮排表達結構。五個規則先記住:冒號後面要空格、縮排只用空格不用 Tab、清單用減號開頭、註解只有單行、字串怕被誤判成布林或數字就加引號。載入別人給的 YAML 檔時,Python 要用 safe_load。把這幾條守住,最常見的幾類 YAML 錯誤都能避開。

這份能力現在已經不是工程師的專利。寫部署與 CI 的後端要懂、管基礎設施的維運要懂、改 API 文件的技術寫手要懂、經營靜態網站或部落格的內容工作者每天都碰得到 frontmatter,就連把工作流程打包給 AI 代理的使用者,設定檔也長同一個樣子。一份格式、六種場景、一套規則,投資報酬率比多數人想像的高。

YAML 是什麼:資料序列化格式,不是程式語言

先處理最常見的誤解:YAML 不是程式語言,它沒有變數、沒有迴圈、也沒有條件判斷。它是一種資料序列化格式,做的事跟 JSON 一樣:把結構化的資料(文字、數字、清單、鍵值對)寫成一份檔案,讓不同的程式、不同的機器之間能互相讀寫。官方規格書對它的定義是一種「對人類友善、跨語言、以 Unicode 為基礎的資料序列化語言」,用途從設定檔、網路訊息到物件持久化與資料稽核都涵蓋。

「序列化」聽起來抽象,換個說法就清楚了:你在程式裡有一個物件,裡面有訂單編號、通路與品項清單;想把它存成檔案、傳給別的服務、或放進版本控制,就需要一種雙方都看得懂的格式。YAML 扮演的就是這個角色,差別只在於它的長相是為「人直接閱讀與編輯」而設計,機器解析的效率排在後面。

把 YAML 放回格式光譜上看,會更理解它的取捨。XML 用成對標籤表達一切,表達力強但視覺噪音大,改一份設定要在開頭與結尾各改一次;JSON 簡潔、解析快,是資料交換的預設選擇,但它不支援註解、多行文字要寫成帶跳脫符號的單行,拿來當「人要反覆編輯的設定檔」很吃力。YAML 補的正是這個缺口:註解、多行字串、縮排層次全都是一等公民,代價是解析器要做更多判斷、規則邊界也比較多,這也是為什麼 YAML 的陷阱清單比 JSON 長得多。

左右捲動檢視完整表格

特性XMLJSONYAML
註解支援不支援支援(單行)
多行字串支援需跳脫符號原生支援 | 與 >
結構表達成對標籤大括號與逗號縮排
人手編輯費力中等最省力,但對縮排敏感

所以「該用哪個格式」的答案通常不需要二選一,重點在分工:程式與程式之間交換資料,JSON 依然穩;要進版本控制、要被人反覆修改、要在 code review 裡被逐行檢視的設定,YAML 的優勢才會發揮。接下來的所有場景,從部署清單到文章 frontmatter,背後都是同一個判準。

遞迴縮寫:YAML Ain't Markup Language

YAML 這四個字母是遞迴縮寫,展開後是「YAML Ain't Markup Language」,翻成中文是「YAML 不是標記語言」。這個名字是刻意取的:標記語言(例如 HTML 或 XML)關心的是「文件怎麼呈現、怎麼標註」,而 YAML 從第一天起就想表達「資料本身的結構」。在 YAML 檔案裡你不會看到成對的標籤,只會看到乾淨的鍵值對與縮排。

它的起源可以從官方規格書的歷史章節讀到:Clark Evans、Oren Ben-Kiki 與 Ingy döt Net 三人在 2001 年前後透過 yaml-core 郵件論壇展開協作設計,歷經約三年,在 2004 年初發表 YAML 1.0 規格。同一份文件也記錄了兩個生態里程碑:第一個 YAML 框架在 2001 年以 Perl 寫成,而 Ruby 是第一個把 YAML 框架直接內建出貨的語言。對現在的使用者來說,這段歷史的重點只有一個:YAML 的設計動機是「讓人直接編輯資料」,這個基因決定了它後來的所有優點與所有陷阱。

縮排即結構:少掉大括號與逗號的讀法

同樣一份資料,用 JSON 寫是這樣:

{"order": {"id": "A-1048", "channel": "web", "items": [{"sku": "TP-200", "qty": 2}]}}

用 YAML 寫是這樣:

order:
  id: A-1048
  channel: web
  items:
    - sku: TP-200
      qty: 2

兩者表達的資料完全相同,但第二種你可以在會議上直接唸出來,改一個欄位也不會少一個逗號就整份報錯。這就是 YAML 能攻佔設定檔市場的原因:工程師寫、主管審、偶爾行銷同事也要改一個文案參數,門檻夠低。代價則是所有結構資訊都壓在縮排上,只要多一個空格或少一個空格,意義就變了,這也是後面兩個章節要反覆拆解的主題。

YAML 1.2 是 JSON 的嚴格超集合

JSON 與 YAML 的關係,官方規格講得非常明白:2009 年發表的 YAML 1.2,核心目標就是讓 YAML 成為 JSON 的嚴格超集合(strict superset),也就是說,任何合法的 JSON 文件本身就是合法的 YAML 文件。你完全可以把一段 JSON 原封不動貼進 .yaml 檔案,YAML 1.2 解析器吃得下去;反方向則不成立,YAML 多出來的註解、多行字串與縮排風格 JSON 並不支援。

1.2 版還做了一件影響深遠的事:移除多項有問題的隱式型別建議,這一點等到談挪威問題時再展開。至於你在文件上偶爾看到的 1.2.2,它是 2021 年 10 月 1 日發布的修訂版,官方明言相對 1.2 沒有規範性變更,目的只是修正錯誤與增補清晰度,所以「YAML 1.2」與「YAML 1.2.2」在語言特性上可以視為同一個版本。

語法規則精要:五條就夠

YAML 的語法細節不少,但日常會用到的核心只有五條。把這五條練成反射,面對常見的設定檔就能直接動手。

規則一:區塊鍵值對的冒號後面要空一格

本文使用的區塊(block)鍵值對寫法是「鍵: 值」,冒號後面要跟著至少一個空白字元。規格書的原文其實帶著「通常」(Normally)的語氣:冒號作為值的指示器時,必須以空白與值分隔;例外是 JSON 風格的 flow 寫法:當大括號裡的鍵本身是 JSON 形式(例如引號包住的字串)時,冒號後面可以緊貼著值,所以 {"a":1} 這樣的寫法是合法 YAML。這個限制帶來一個好處:在區塊寫法的純量裡,冒號只要後面不接空白,就可以直接出現在文字中,所以網址與時間戳記不用加引號也沒事。

api_endpoint: https://api.example.com/v1
created_at: 2026-10-19T09:30:00

它同時埋了一個新手一定會踩的坑:a:1 不會被解析成鍵 a 等於 1,而是被讀成一個內容為「a:1」的單一字串。規格書甚至特別點名這個「a:1」案例是常見的混淆來源。看到「明明有冒號卻讀不到鍵」的錯誤,先檢查冒號後面是不是少了空白。

規則二:縮排只用空格,全面禁用 Tab

YAML 的結構由縮排決定,而規格書對縮排的規定是:行首的零或多個「空格」字元,並且「為維持可攜性,縮排不得使用 Tab」,理由是不同系統對 Tab 的處理不一致。這在所有常見格式裡幾乎是最嚴格的一條:Python 只禁 Tab 與空格混用,YAML 是直接全面禁止。

YAML 官方 FAQ 的存檔版本把理由講得更白:Tab 被宣告「全面禁止」,因為不同編輯器與工具對 Tab 的處理不同,而縮排對 YAML 解讀太過關鍵,這個問題複雜到官方根本不想嘗試解決。實務上的自保動作有兩個:把編輯器設定成按 Tab 鍵輸入固定數量的空格(通常是兩格),以及讓編輯器把行尾空白與 Tab 顯示出來。「昨天還能跑、今天突然壞」的 YAML 問題,常見的原因之一就是檔案裡混進了 Tab。

規則三:清單用減號,巢狀靠縮排

清單(sequence)的每一項以減號加一個空白開頭,同一層的減號必須對齊。要表達「清單裡再包一層鍵值對」,就把內容往右縮排:

deploy:
  targets:
    - env: staging
      branch: develop
    - env: production
      branch: main

判讀技巧只有一句話:往右一格就是往下一層。你不需要算逗號、不需要找括號,只要沿著縮排的層次往內讀。也因為層次全靠縮排表達,團隊最好約定統一的縮排寬度(兩格是主流),並交給編輯器自動維持,不要有人用兩格、有人用四格,那種檔案沒有人能安全地改。

清單與鍵值對混用時有一個新手常見的糊塗點:減號那一行自己就是一個節點,它下面同一層的鍵值對都屬於同一個項目。上面的例子裡 env 與 branch 都縮在減號底下,所以是一組;而這裡的對齊是硬規則:此例中 branch 若未與 env 對齊,多縮或少縮一格都會直接造成 YAML 語法錯誤(env: staging 的值已是純量,底下不容許再掛更深一層的欄位),部署根本不會開始。遇到「清單裡的項目讀不到某個欄位」或「一直報語法錯」,先確認欄位跟減號下第一個鍵是否對齊,常見的毛病就是這裡歪了一格。

清單內有兩個獨立項目,每個項目的 env 與 branch 對齊並歸在同一組
減號代表一個清單項目;同一項目內的 env 與 branch 是同層欄位,必須對齊。

規則四:註解只有單行

YAML 的註解由井字號開始,但井字號要能當註解,有位置條件:出現在行首,或與前面的內容之間隔著空白,這一行從井字號到行尾才會被忽略。藏在文字裡的井字號屬於內容——hello#world 會被原樣讀成字串,"hello # world" 這種引號內的井字號也是內容,兩者都不會被當成註解吃掉。YAML 沒有區塊註解:想註解三行就要打三個井字號,想暫時停用一段設定,常見做法是把整段複製到檔案外的草稿,或逐行加上註解。這個限制常讓寫慣其他語言的人不適應,但也讓 YAML 檔案的每一行都保持「要嘛生效、要嘛整行註解」的清楚狀態,審起來反而單純。

# 台灣區部署設定
region: ap-northeast-1   # 暫借東京節點
# replicas: 4            # 週年慶前再打開

規則五:型別會自動推斷,引號是保險

不加引號的值,解析器會試著自動判斷型別:看起來像整數就當整數、像浮點數就當浮點數、空值就是 null。YAML 1.2 的 Core Schema(規格建議處理器預設採用的解析規則集)對布林的判定只認 true、True、TRUE 與 false、False、FALSE。大多數時候這個機制很方便,但它也是 YAML 最著名的陷阱來源:當你的資料「長得像布林、長得像數字、或長得像 null」卻其實是字串時,就必須加引號。

version: "1.10"     # 加引號:這是字串,不是數字
answer: "no"        # 加引號:這是兩個字的字串,不是布林
empty: ""           # 加引號:空字串,與 null 不同

一句話記法:這個值如果印出來應該長得跟現在完全一樣,就加引號。版號、密碼、國碼、開頭是零的編號,全都屬於該加引號的族群。

挪威問題:no、yes、on、off 的布林陷阱

假設電商團隊的設定檔裡有這樣兩行:

store:
  country: NO
  featured: off

在受 YAML 1.1 解析規則影響的工具裡,country 的值不會是字串「NO」,而是布林值 false。挪威的國碼就這樣把你的商店設定搞壞了,這個案例在工程圈被稱為「挪威問題」(Norway Problem),是所有 YAML 教學都繞不開的經典事故。

YAML 1.1 的官方布林對照表

這個問題不是都市傳說,它寫在官方文件裡。YAML 1.1 的布林型別定義(yaml.org 的 bool 型別頁)列出的解析規則包含下列全部寫法:

左右捲動檢視完整表格

寫成布林 true寫成布林 false
y、Y、yes、Yes、YESn、N、no、No、NO
on、On、ONoff、Off、OFF
true、True、TRUEfalse、False、FALSE

也就是說,在 1.1 的規則下,no 是 false、yes 是 true、off 是 false、on 是 true,連單獨一個 y 或 n 都算。官方文件自己的範例裡就放了一行 answer: NO,把它解析成布林。規則本身訂得清清楚楚,問題出於它對人類直覺太不友善。

國碼 NO 為什麼會變成 false

機制拆開來看只有三步。第一步,你寫 country: NO,因為沒加引號,解析器不會直接認定它是字串,而是先拿去比對隱式型別規則。第二步,1.1 的布林規則表裡明確列著 NO。第三步,比對命中,資料在進到你的程式之前就被換成了布林 false。你的程式碼從頭到尾沒有出錯,錯的是資料在入口處就被轉換了。

實際受害的不只是挪威。設定檔裡的 yes/no 問答值、開關欄位 on/off、甚至資料表裡的單字欄位,都可能在不同工具上得到不同結果。更麻煩的是它壞得安靜:解析不會報錯,程式照常執行,只是某個判斷永遠走進奇怪分支,這種問題在大型系統裡要抓很久。

除錯時有個實用的思考順序。看到「條件永遠不成立」或「開關永遠打不開」這類症狀,先把 YAML 檔載入後的實際結果印出來看,別盯著檔案原文猜;一旦你印出 country 的值,看到的竟是 false 而非 NO,答案就自己浮出來了。第二個順位才是查工具的版本立場:同一份檔案在不同解析器手上結果不同,幾乎都是 1.1 與 1.2 的隱式型別差異。把「印出來看」養成反射,能省下大量對著檔案發呆的時間。

接手既有專案時,值得花十分鐘做一次存量盤點:把設定檔裡所有沒加引號、又長得像 yes、no、on、off、y、n 的值全部列出來,逐一確認欄位預期的型別:預期是字串就補引號,預期是布林就改用 true 或 false,改完再驗證消費端的行為。這種盤點最好在事故發生前做,因為挪威問題的破壞力來自「上線前完全沒有徵兆」,而補引號的成本只有幾秒鐘。盤點順手把縮排裡的 Tab 一起清掉,兩件事故源一次處理完。

1.2 已經修正,但生態還沒跟上

規格面其實早就處理了。YAML 1.2 的 Core Schema把隱式布林收窄到 true、True、TRUE、false、False、FALSE 六種組合,yes、no、on、off 一律不再是布林,而 Core Schema 正是規格建議處理器預設採用的規則集。

無奈工具鏈還停在舊世界。Python 生態最常用的 PyYAML,在官方網站的自介裡就寫明自己是「完整的 YAML 1.1 解析器」,這正是 Python 讀 no、yes、on、off 會得到布林的直接原因。這不代表 PyYAML 不能用,而是你必須知道手上工具講的是哪一版規則。

防禦工具有三層。第一層是加引號,任何可能被誤判的字串一律引號伺候,這是最直接的做法。第二層是靜態檢查,yamllint 的 truthy 規則文件明文寫著:YAML 1.1 會把 yes、no、on、off 的組合視為 truthy、YAML 1.2 只認 true、True、TRUE 與 false、False、FALSE,讓它在存檔當下就跳出警告。第三層是在文件開頭以 %YAML 1.2 指令明示版本,但要留意,明示版本不保證解析器換一套規則:以 PyYAML 為例,即使檔案開頭寫了 %YAML 1.2,它仍以 1.1 的規則解析,country: NO 照樣變布林;這層防禦要先確認工具實際支援的行為才算數。順帶一提,GitHub Actions 的觸發鍵 on: 正好撞在這條規則上:1.1 解析器會把 on 當布林,所以有些團隊的 lint 設定需要調整,yamllint 也提供 check-keys 選項,停用對 mapping 鍵的這項檢查。

未加引號的 NO 在 YAML 1.1 布林規則會變成 false,在 YAML 1.2 Core Schema 則是字串
解析規則決定型別;若欄位預期保留字串,使用引號比只宣告 YAML 版本更直接,仍須確認工具行為。

多行字串:豎線與折疊符號的選擇

把一段長文字放進設定檔,YAML 給你兩種風格,符號取自它們的長相:豎線 | 是 literal block scalar(字面區塊),大於符號 > 是 folded block scalar(折疊區塊)。官方規格對兩者的定義差別在換行的處理方式。

release_note: |
  修正結帳頁在小螢幕上的按鈕跑版。

  調整物流串接的逾時設定。

summary: >
  這次改版涵蓋結帳流程
  與物流串接兩個區塊,
  共兩項修正。

豎線版本會保留每一個換行,讀出來就是三行文字(中間空一行也會保留);折疊版本的一般規則是相鄰內容行之間的換行折成空格,整段收斂成一行,但空行與更深縮排的行,周圍的換行會保留下來;上面的例子沒有觸發這些保留條件,所以讀出來是一行。選擇原則很好記:內容是程式碼、日誌、指令稿這種「換行本身有意義」的文字,用豎線;內容是自然語言段落,希望 YAML 檔裡方便閱讀但讀出來是連續句子,用折疊。

兩種風格選錯的後果通常不嚴重,但除錯起來很磨人:shell 指令用折疊寫,三行被併成一行,指令照樣執行只是行為奇怪;文案用豎線寫,該折行的段落變成斷成好幾行的短句,前端渲染出來就是排版事故。養成習慣的方法是在 code review 時多問一句「這段文字讀出來換行長什麼樣」,問兩三次之後,選擇就會變成直覺。

豎線保留內容行的換行,折疊符號把一般相鄰內容行的換行折成空格
此圖只示意一般相鄰內容行。折疊風格遇到空行或更深縮排時仍可能保留換行,結尾換行另由 chomping 指示器控制。

chomping 指示器:減號與加號控制結尾換行

多行字串的結尾換行怎麼算,由 chomping 指示器控制,共有三種行為:規格書定義的 CLIP(預設,僅保留一個尾端換行)、STRIP(減號 -,移除所有尾端換行)與 KEEP(加號 +,保留全部尾端換行)。規格的 Example 8.1 正是用 |、>1、|+、>1- 四種標頭示範對應的輸出。

左右捲動檢視完整表格

寫法尾端換行行為典型用途
|保留一個換行(CLIP,預設)一般多行文字
|-移除所有尾端換行(STRIP)產生 shell 指令、金鑰內容
|+保留全部尾端換行(KEEP)保留文字結尾的多個空行

至於 |-:需要讀出來不含尾端換行的字串時就用它,例如把指令或金鑰內容寫成多行、讀出來不帶多餘換行;該不該去掉尾端換行,依接收端的要求決定,有些接收端本來就預期字串以換行結尾,先確認再選。折疊風格同樣可以加上這些指示器,行為對應相同。

錨點、別名與合併鍵:YAML 的重複使用機制

同一份設定裡同樣的區塊出現第三次時,就該認識 YAML 的重複使用機制。規格的說法是:重複出現的節點先以 & 符號標記為錨點(anchor),之後以 * 符號的別名(alias)引用同一個節點,資料不必複製貼上。

defaults: &db_defaults
  adapter: postgres
  database: shop_default
  pool: 5

production:
  database: shop_prod
  <<: *db_defaults

staging:
  database: shop_stage
  <<: *db_defaults

這裡的 << 是合併鍵(merge key)。它不是 1.2 Core Schema 的一部分,而是 YAML 1.1 型別註冊表裡的 merge 型別:官方型別文件的定義是把一或多個指定 mapping 的鍵值對插入當前 mapping,而且已存在的鍵不會被覆蓋。上面的例子裡,defaults 雖然也帶了 database: shop_default,但 production 自己宣告了 shop_prod,「已存在的鍵不被覆蓋」就作用在這裡,合併進來的預設值蓋不掉它;production 沒設的 adapter 與 pool,才由 defaults 遞補,staging 同理。這個機制不在 1.2 core schema 內,但主流工具仍廣泛支援,使用前確認你的解析器吃不吃它,是基本素養。

錨點與合併鍵的魅力在「改一處、全生效」,風險也在同一處:當合併層數一多,讀檔的人要在腦中展開好幾層間接引用才能知道最終值是什麼,除錯成本快速上升。實務建議是讓錨點停留在「一層引用」的單純用法,被引用的區塊放在檔案開頭集中管理;一旦發現自己開始寫「錨點引用另一個錨點的合併結果」,通常是時候把共用值拆成範本變數,交由部署工具處理,別讓 YAML 檔自己長成一門小語言。

命名也有小學問。錨點名稱最好描述「這組值的角色」,避開「它出現的位置」這類代號,像上面的 db_defaults 一眼就知道是資料庫預設值;命名成 a、base2 這種代號,兩個月後連作者本人都要重新推敲。多環境設定(正式、預發、測試)共用一組基礎值、各自覆蓋少數欄位,是錨點與合併鍵最經典也最安全的應用形態,環境之間的差異一眼就能對照出來,審查時不用來回捲動。

副檔名:.yaml 還是 .yml

兩種副檔名都合法,但建議有明確答案。YAML 官方 FAQ 的存檔版本對「有沒有官方副檔名」的回答是:盡可能使用 .yaml。而工具端多半兩種都收,例如 GitHub Actions 的官方快速入門就明文允許 .yml 或 .yaml 兩種副檔名。

實務做法很簡單:新專案一律用 .yaml,與官方建議一致;接手舊專案時入境隨俗,跟著既有慣例走,不要一半一半。真正該避免的是同一個 repo 裡兩種副檔名混用,那會讓「找設定檔」這件小事永遠多一次猜測。至於網路上流傳的各種副檔名歷史成因說法,在現行官方文件裡找不到依據,不需要寫進你的團隊規範。

六大應用場景:一份設定檔走遍部署、CI、文件與內容

學會語法之後,更重要的是知道 YAML 在真實世界的位置。以下六個場景都有官方文件背書,也代表六種「把結構化資料交給機器」的工作型態。

這六個場景的共同點是載體,而不是執行模型:這些工具以 YAML 表達各自的設定或流程,執行順序、套用方式及版本追蹤由工具與團隊流程決定。其中 Kubernetes 的 manifest 走宣告式路線:你寫的是「最終狀態應該長這樣」的描述,要三個副本就寫數字三,至於怎麼到達,交給叢集;GitHub Actions 的 workflow 與 Ansible 的 playbook 則是流程定義,官方文件明載 steps 依序執行、play 裡的 tasks 由上而下逐項執行,先後順序本身就是語意的一部分。共同的實際好處來自純文字:可以版本控制、可以差異比對;而 YAML 的可讀性剛好讓內容連非作者都看得懂,這是它在這個位置上難以被取代的原因。

Kubernetes:manifest 的慣例格式

Kubernetes 官方文件寫得很直白:按慣例,manifest 以 YAML 撰寫(也可以用 JSON),而 kubectl 之類的工具會把 manifest 內容轉換成 JSON 或其他支援的序列化格式,再透過 API 發出請求。換句話說,你寫 YAML 是給人與版本控制看的,機器在送出請求前自己會轉換。官方文件列出的必填欄位有四組:apiVersion、kind、metadata(其中的 name 等資料用來識別物件)與 spec(描述你要的狀態);其中 apiVersion、kind 與 metadata.name 是辨識一份 manifest 時最常看到的組合,一份完整的 Deployment 大致長這樣:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-front
spec:
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: nginx

對新手來說,先記三件事:manifest 描述你要建立的 Kubernetes 物件、apiVersion 與 kind 決定這是什麼資源、metadata.name 是它在叢集裡的身分;spec 的結構則因物件種類而異,照著對應的 API 參考文件寫。manifest 是純文字,所以變更有機會走版本控制與 code review,但「部署歷史就是提交歷史」要成立,前提是團隊真的把清單納入版本控制並以它為部署來源;這是流程帶來的保證,不是格式自動送上的。做到之後,回溯問題時才不需要猜當時誰在主控台按了什麼。

Docker Compose:一份 compose.yaml 描述整個應用棧

Docker Compose 官方文件的定位是:用單一 YAML 設定檔管理應用程式棧的 services、networks 與 volumes,一個指令就能建立並啟動全部服務;這份檔案的正式名稱是 compose.yaml,遵循 Compose Specification 的規則。對小型團隊來說,它是最容易上手的「一份檔案講清楚整個服務拓撲」的練習場:

services:
  web:
    image: nginx
    ports:
      - "8080:80"

GitHub Actions:CI 流程本身就是一份 YAML

想在 GitHub 上做自動化,你寫的不是腳本,是一份設定。GitHub Actions 官方語法文件的第一句話就是:workflow 是可設定的自動化流程,「必須建立 YAML 檔」來定義它;檔案放在儲存庫的 .github/workflows 目錄,副檔名必須是 .yml 或 .yaml。還不熟 Git 與儲存庫操作的人,可以先回頭看GitHub Repo 入門指南再回來寫 workflow。

name: deploy
on:
  push:
    branches: [main]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Build started"

注意上面那個 on: 鍵。它正好是前面提過的 1.1 布林規則會掃到的字,各家工具對它的處理不一致,這也是 YAML 版本差異最貼身的實例:同一份檔案,官方文件說它是觸發條件,舊解析器覺得它是布林。遇到檢查工具對它跳警告時,你已經知道原因了。

Ansible:以最少語法表達的 playbook

Ansible 官方手冊對 playbook 的描述是「以 YAML 格式表達、語法需求極少」,並建議不熟 YAML 的人先讀官方的語法概覽、在編輯器裝上 YAML 外掛再上手。playbook 最外層清單的每個破折號是一個 play,縮排往下展開主機與任務;play 裡 tasks 清單的每個破折號則是一個 task,由上而下依序執行:

- hosts: web
  tasks:
    - name: Install nginx
      package:
        name: nginx
        state: present

OpenAPI:API 文件的另一種載體

OpenAPI 官方學習文件說明:OpenAPI Description 以一份或多份文字文件撰寫,每份文件代表一個 JSON 物件,可以用 JSON 或 YAML 格式;入口文件慣稱 openapi.json 或 openapi.yaml。同一份規格用 YAML 呈現時,路徑與參數的層次沿縮排展開,讀起來比 JSON 少掉大量引號與逗號,非常適合需要頻繁檢視修改的 API 契約。

靜態網站 frontmatter:Jekyll、Hugo 與 Astro

寫內容的人接觸 YAML 的入口是 frontmatter:檔案最開頭以三條虛線包夾的中繼資料區塊。Jekyll 官方文件規定 front matter 必須是檔案的第一個東西、以三條虛線包夾的合法 YAML,任何含這個區塊的檔案都會被 Jekyll 當特殊檔案處理。Hugo 官方文件則支援 JSON、TOML、YAML 三種序列化格式,並依分隔符判別格式:--- 包夾的是 YAML、+++ 包夾的是 TOML、以大括號包夾的是 JSON,同一個站體裡三種格式可以並存。實務上建議全站統一用一種,編輯器外掛與團隊範本才不會在縮排規則上彼此打架。到 Astro 的內容集合,glob() loader 可以從檔案系統載入 Markdown、MDX、Markdoc、JSON、YAML 或 TOML 檔,.md 內容項目同樣以 --- frontmatter 定義 title 與 slug 等屬性,slug 可以覆蓋自動生成的 id:

---
title: 週年慶 landing page 改版紀錄
slug: anniversary-landing-redesign
---

這就是為什麼行銷與內容工作者也該懂一點 YAML:你每天更新的文章屬性、社群卡片的描述文字、導覽列的項目順序,很多都住在這個小小的三虛線區塊裡,而它的一切規則都跟你同事在寫的部署設定一模一樣。

frontmatter 的欄位名稱由各家 generator 自行定義,同一個概念在不同系統裡可能叫不同名字,搬移內容時要對照目標系統的文件確認,不要假設欄位自動通用。寫作時值得建立的習慣是把 frontmatter 當成「這篇內容的資料庫欄位」看待:標題、描述、網址代稱一一填好、拼寫一致,網站長大之後要重構或搬家的那天,你會感謝當初每篇文章都有乾淨一致的中繼資料。反過來說,frontmatter 亂掉的網站,往往在規模變大後才付出代價。

結構化設定與 AI 可讀性:AI 產品也選了 YAML

近兩年 YAML 多了一批新使用者:AI 工具。這個選擇與 SEO 領域的姐妹篇是同一個思路:結構化資料把頁面資訊交給搜尋引擎、llms.txt 把網站重點交給生成式引擎,而 YAML frontmatter 把「何時該用這個能力」的中繼資料交給 AI——把機器要判讀的資訊放進明確結構,通常比埋在自然語言裡更容易被穩定解析。要強調的是,這是設計上的推論,實際效果仍取決於各工具的實作方式。

Anthropic 的 SKILL.md:官方文件親自示範

最有代表性的官方實例是 Anthropic 的 Agent Skills。官方規範寫明:每個 skill 的 SKILL.md 由兩部分組成,--- 標記之間的 YAML frontmatter(name、description 等欄位,告訴 Claude 何時該用這個 skill),加上其後的 Markdown 內容;skills 的設定就是透過 SKILL.md 頂部的 YAML frontmatter 完成。

值得注意的設計在於分工:frontmatter 提供 name、description 等中繼資料與設定,負責告訴 Claude「何時該用這個 skill」;Markdown 正文則是 Claude 執行 skill 時遵循的指令。兩個部分都會被機器讀取——frontmatter 的結構化欄位便於解析、比對與篩選,正文承載完整的執行指令。想深入這套機制的人,可以接著讀Claude Skills 完整教學。

把這個結構借來自己的專案也成立:任何需要 AI 反覆遵循的規範,都可以拆成「結構化欄位 + 自由文字」兩層。機器要判讀的觸發條件、適用範圍、參數限制放欄位;需要判斷力的流程細節放文字。這個拆法跟工程師寫設定的思路完全一致:確定性的部分交結構,模糊性的部分交文件。前後端協定、內容審核規則、客服回覆的邊界條件,都適用同一套骨架。

明確結構 vs 自然語言:同一套邏輯的三個出口

把這個觀念放到你的工具箱裡,會發現各種「讓 AI 讀得懂」的檔案都在結構化光譜上:AGENTS.md 用 Markdown 段落與標題組織代理指令,走的是彈性路線;MCP 的設定檔用 JSON 表達伺服器連線,走的是嚴格路線;SKILL.md 的 YAML frontmatter 則介於中間:結構化欄位承載「何時該用」的中繼資料,自由文字承載執行時遵循的指令。選擇的判準不是流行,而是「這份資訊要不要被程式可靠地拆解」:要,就往結構端靠;不要,就保留自然語言的彈性。

這也是工程師寫 YAML 的經驗對 AI 時代依然值錢的原因:懂得把模糊需求拆成欄位、列舉與層級的人,無論面對的是部署清單還是 AI 設定,產出的檔案都更不容易出錯。格式會換,拆解能力不會。

Python 讀 YAML 的資安底線:safe_load

最後一個主題是資安,也是最容易省略、後果最重的一段。Python 讀 YAML 的標準套件是 PyYAML,而它有兩個載入函式,命運大不相同。先講結論:任何不是你本人寫的 YAML 檔,一律用 yaml.safe_load。

import yaml

# 不要這樣載入不受信任的檔案
cfg = yaml.load(open("config.yaml"))

# 應該這樣
cfg = yaml.safe_load(open("config.yaml"))

PyYAML 官方文件的警告寫得毫不客氣:對任何來自不受信任來源的資料呼叫 yaml.load 並不安全,yaml.load 與 pickle.load 一樣強大,可能呼叫任意 Python 函式;應改用 yaml.safe_load。差別在於 SafeLoader 只支援標準 YAML tags、不會建構任意 Python 物件。附帶一提,PyYAML 從 6.0 起 Loader 參數改為必填,上面那行裸呼叫的 yaml.load 在現行版本會直接報錯;如今真正危險的寫法,是把 Loader 明確指定成 UnsafeLoader 這類等級,結論不變:不是你本人寫的檔案,一律 safe_load。

攻擊場景不難想像:某個公開 repo 收到一份「好心修正」的設定檔,內容裡藏著指定 Python 物件的 tag,CI 流程用 yaml.load 載入的瞬間,攻擊者的程式碼就在你的 runner 上執行了。官方警告針對的正是「不受信任來源」這個情境,判斷標準應該從嚴:來源不是你,就用 safe_load,沒有例外。順帶記住前面提過的:PyYAML 是完整的 YAML 1.1 解析器,資安與挪威問題兩條防線,在 safe_load 與引號這兩個習慣上一次補齊。

YAML 的引號與縮排處理解析問題,safe_load 則限制載入可建構的物件,兩者各有目的
safe_load 限制任意 Python 物件建構,並不把 PyYAML 的 1.1 型別規則改成 1.2;型別與載入安全要分別檢查。

落實到團隊流程有三個動作。程式碼層面,專案裡直接約定只出現 safe_load,把這條寫進程式碼規範。審查層面,code review 看到別人提交的 YAML 相關程式時,掃一眼載入函式的名字,這個檢查只花三秒。供應鏈層面,CI 會載入的外部設定檔來源要列清單,清單外的來源不進 runner。資安習慣的價值在於平時看不見,而 YAML 設定檔恰恰是「人人都能改、人人都以為它只是資料」的檔案,把關的責任自然落在寫載入程式的人身上。

上線前檢查表:六種事故現場與攔截手法

把整份教學收斂成一張檢查清單。以下六個錯誤是 YAML 常見的事故現場,每一項都對應前面章節的機制說明。

  • 縮排混入 Tab:編輯器按下去是 Tab 還是空格,眼睛看不出來,解析器分得很清楚。把編輯器設成「Tab 鍵輸入空格」,一次斷根。
  • 冒號後面沒有空白:a:1 是字串不是鍵值對,這是規格書點名的經典混淆。看到鍵讀不到,先檢查冒號。
  • 該加引號的沒加引號:no、on 這類值在 1.1 工具下會變布林,國碼 NO 是最著名的受害者。判斷標準:值印出來該跟現在完全一樣,就加引號。
  • 忽略版本差異:1.2 的 Core Schema 不再把 yes、no、on、off 解析為布林(true、True、TRUE 與 false、False、FALSE 仍是布林),但你手上的工具(例如 PyYAML)可能還在講 1.1。知道工具的版本立場,才知道哪些寫法危險。
  • 合併鍵疊床架屋:錨點與 << 用到第二層以上,可讀性就開始崩壞。共用值超過一層時,改用部署工具的變數機制處理。
  • 用 yaml.load 載入來路不明的檔案:不受信任的 YAML 一律 safe_load,這是官方警告的等級,不是建議的等級。

驗證方法分兩級。手動級:把檔案丟給 yamllint 跑一次,truthy 與縮排問題會直接列出;它的規則文件同時標明 1.1 與 1.2 的布林差異,等於把最容易踩的雷做成清單。日常級:在編輯器裝 YAML 外掛,Ansible 官方手冊也建議這樣做,存檔當下就有語法回饋,比任何事後檢查都便宜。兩級都做的團隊,有助於在出貨前提早發現部分語法及風格問題。

回到開場那個場景:同事說「就是縮排而已」,現在你知道這句話對了三分之二。縮排是骨架,冒號與引號是關節,載入函式是最後一道閘門。把五個規則練成反射、對 1.1 與 1.2 的差異保持警覺、對不受信任的檔案一律 safe_load,YAML 就會是整個工具鏈裡最安靜可靠的一環:它不搶戲,但部署、CI、文件與 AI 設定全都站在它上面。

常見問題

YAML 是程式語言嗎?
不是。YAML 是一種資料序列化格式,官方定義為對人類友善、跨語言、以 Unicode 為基礎,用縮排表達結構。它沒有變數、迴圈或條件判斷,做的事與 JSON 同類:把結構化資料寫成檔案,供程式與程式之間互相讀寫。
YAML 可以完全取代 JSON 嗎?
兩者是分工而非取代。YAML 1.2 是 JSON 的嚴格超集合,任何合法 JSON 文件本身就是合法 YAML,所以把 JSON 貼進 .yaml 檔可行;但機器對機器的資料交換 JSON 仍是主流,需要註解、多行字串與人工反覆編輯的設定檔才是 YAML 的主場。
.yaml 和 .yml 要用哪一個?
新專案建議統一用 .yaml,與 YAML 官方 FAQ(存檔版本)的建議一致;工具端大多兩種都收,例如 GitHub Actions 就明文允許 .yml 或 .yaml。重點是同一個專案內不要混用,避免找設定檔時永遠多一次猜測。
為什麼 YAML 的縮排不能用 Tab?
規格書明文規定:縮排由行首的空格組成,為維持可攜性不得使用 Tab,理由是不同系統對 Tab 的處理不一致。官方 FAQ(存檔)更直接把 Tab 宣告為全面禁止,因為縮排對 YAML 的解讀太過關鍵。把編輯器設成按 Tab 鍵輸出空格,可以一次斷根。
Python 讀 YAML 一定要用 safe_load 嗎?
只要檔案來源不是你本人,一律用 yaml.safe_load。PyYAML 官方文件警告:對不受信任來源的資料呼叫 yaml.load 並不安全,它與 pickle.load 一樣強大,可能呼叫任意 Python 函式;safe_load 的 SafeLoader 只支援標準 YAML tags,不會建構任意 Python 物件。
什麼是 YAML 的挪威問題?
YAML 1.1 的官方布林規則把 no、yes、on、off(含各種大小寫與單字母 y、n)都解析成布林值,因此挪威的國碼 NO 會被轉成 false。YAML 1.2 的 Core Schema 已收窄為只認 true、True、TRUE 與 false、False、FALSE,但 PyYAML 等主流工具仍是 1.1 解析器;處理時先確認欄位預期型別,字串補引號、布林改用 true 或 false。

操作步驟

  1. 把編輯器設定成按 Tab 鍵輸出固定空格(兩格為主流),並安裝 YAML 語法外掛,讓縮排與 Tab 兩大事故源在存檔當下就被擋下。
  2. 開新檔統一用 .yaml 副檔名;寫入內容時守住五條:冒號後空一格、縮排只用空格、清單用減號開頭、註解單行井字號、可能被誤判的字串加引號。
  3. 長得像 no、on、yes、off 或以零開頭的值,先確認欄位預期型別:字串補引號、布林改用 true 或 false,防範 YAML 1.1 工具的布林轉換(挪威問題);交付前用 yamllint 跑一遍,truthy 與縮排問題會直接列出。
  4. Python 端載入一律寫 yaml.safe_load,把別人提供的檔案全部當不受信任來源處理;code review 時多花三秒確認載入函式的名字。

Whoops 巫普斯科技有限公司

專注技術 SEO、GEO/AEO 與 AI 搜尋實務。本站文章以可驗證資料、公開來源與實作觀察整理而成。

關於 Whoops編輯守則服務內容

想把這篇的方法用在自己的站上?

SEO 健檢、GEO/AEO 引用優化、網頁設計諮詢——把文章裡的方法落地到你的網站。