Baechu Kimchi from Scratch: Salt Ratios, Yangnyeom Paste, and Anaerobic Packing
Learning Objectives
- ✓After this lesson, students will be able to calculate and apply salt ratios as a percentage of cabbage weight for reproducible wilting results (3.5% of raw cabbage weight, with 70/30 direct-to-brine split)
- ✓After this lesson, students will be able to prepare yangnyeom paste with gram-precise measurements (50 g gochugaru, 30 g saeujeot, 25 g garlic, 10 g ginger, 15 mL fish sauce, 100 mL rice paste per 1 kg raw cabbage)
- ✓After this lesson, students will be able to execute proper anaerobic packing technique including vessel selection, air displacement, liquid coverage verification, and appropriate headspace (3–5 cm)
- ✓After this lesson, students will be able to assess wilting readiness using the bend test and evaluate Day 0 kimchi quality through a multi-point checklist (pH, taste, texture, color, aroma)
- ✓After this lesson, students will be able to record a complete Day 0 fermentation journal entry including batch ID, salt percentage, ambient temperature, initial pH, vessel type, and sensory baseline
Baechu Kimchi from Scratch: Salt Ratios, Yangnyeom Paste, and Anaerobic Packing
In Lesson 1, you built a 육수 from anchovy, kelp, and shiitake — three pillars of Korean umami. You learned that temperature precision matters: kelp at 60–70°C, anchovy at 80–90°C, because alginate breakdown above 75°C turns your stock bitter. You logged tasting notes in your fermentation journal.
That journal is about to earn its keep. Today, you're making kimchi — and from the moment salt hits cabbage, you're managing a living system. Every decision you make today (salt percentage, paste ratios, packing tightness) sets the trajectory for what Lactobacillus will do over the next weeks. Kimchi isn't a recipe you "finish." It's a recipe you launch.
Here's what I want you to internalize: kimchi is controlled rot. That sounds ugly, but it's the truth. You're creating conditions so hostile to harmful bacteria — high salt, no oxygen, acidic environment — that only the tough lactic acid bacteria survive. And those survivors? They produce the sour, fizzy, deeply funky flavor that makes kimchi one of the most complex fermented foods on the planet.
I've made over 300 batches of kimchi in my career. The first 50 were inconsistent because I eyeballed salt. The moment I started weighing everything, my success rate went from maybe 70% to essentially 100%. Today, you'll understand exactly why.
Your fermentation journal — which started as a tasting log for stock — now becomes a batch tracking system. By the end of today, it will hold your Day 0 kimchi data: pH, salt ratio, ambient temperature, vessel type, and sensory baseline. This data feeds directly into Lesson 3, where we'll map your kimchi's pH curve against the Lactobacillus growth stages.
Preparation: Mise en Place (5 Minutes of Setup That Saves 2 Hours of Chaos)
Before you touch the cabbage, get organized. I cannot stress this enough. Kimchi-making has distinct phases with waiting periods between them, and if your yangnyeom ingredients aren't prepped when the cabbage finishes wilting, you'll be scrambling while your cabbage sits exposed and oxidizing.
Ingredients (for ~1.5–2 kg finished kimchi)
| Ingredient | Amount | Notes |
|---|---|---|
| Napa cabbage (배추) | 1 large head, ~2.0–2.5 kg | Firm, heavy for its size, pale green outer leaves |
| Coarse sea salt (굵은 소금) | 70–88 g (3.5% of cabbage weight) | Korean 천일염 preferred; NO iodized table salt |
| Gochugaru (고춧가루) | 50 g per 1 kg cabbage | Coarse-flake Korean chili; NOT cayenne, NOT gochujang |
| Saeujeot (새우젓) | 30 g per 1 kg cabbage | Salted fermented shrimp; adds umami + salt |
| Fish sauce (액젓) | 15 mL per 1 kg cabbage | Korean anchovy sauce preferred |
| Garlic, minced | 25 g per 1 kg cabbage (~8 cloves) | Fresh only — jarred garlic has preservatives that inhibit fermentation |
| Ginger, minced | 10 g per 1 kg cabbage | Fresh, finely grated |
| Scallions (파) | 4–5 stalks, cut into 3 cm pieces | Green and white parts both |
| Sugar or 찹쌀풀 (rice paste) | 15 g sugar OR 100 mL rice paste | Feeds Lactobacillus; I strongly prefer rice paste |
| Optional: Korean radish (무) | 100 g, julienned into 3 cm matchsticks | Adds crunch |
Equipment
- Kitchen scale (must read in grams — non-negotiable)
- Large basin or tub for salting (stainless steel or food-grade plastic)
- Mixing bowl for yangnyeom paste
- Fermentation vessel: glass jar with airlock, onggi pot, or BPA-free plastic container with tight lid
- Food-safe gloves (gochugaru stains and burns cuts)
- pH meter or pH strips (range 3.0–7.0)
- Your fermentation journal from Lesson 1
Why I'm Obsessive About the Scale
Here's a mistake I made in culinary school: a senior chef told me "a generous handful of salt per quarter." My hands are large. My classmate Minji's hands are small. My kimchi was a salt block. Hers was perfect. A "generous handful" for me was about 40 g. For her, about 22 g. That's the difference between 4.5% and 2.5% — the difference between preserved kimchi and a rotting cabbage.
Weigh. Everything.
🤔 Think about it: Why does this recipe specify "coarse sea salt" and explicitly forbid iodized table salt? It's not just tradition.
View Answer
Two reasons — one chemical, one physical. Chemically, iodine is antimicrobial. It's added to table salt specifically to kill microorganisms. That's the opposite of what you want in a fermented food — you need Lactobacillus alive and thriving. Physically, fine table salt dissolves instantly and creates hypertonic pockets that can over-wilt the surface layers while leaving the interior unsalted. Coarse salt dissolves slowly, giving you time to distribute it evenly and allowing osmosis to work gradually from outside in.
Korean 천일염 (sun-dried sea salt) is ideal because it contains trace minerals that Lactobacillus actually uses as micronutrients during fermentation. This isn't mysticism — it's microbiology.
Phase 1: Quartering and Salt-Wilting (Active Time: 15 min / Waiting: 6–8 hours)
Selecting Your Cabbage
Pick up the cabbage. It should feel heavy for its size — that means tightly packed leaves with high water content. Loose, light heads have too much air between leaves, and the salt can't do its job evenly.
Look at the cut base. If it's dried out and brown, the cabbage has been sitting in the store too long. You want a moist, creamy-white cut end. The outer leaves should be pale green, not yellowed. Yellow means the cabbage is converting its starches — fewer sugars available for your lactic acid bacteria.
Quartering Technique
- Place the cabbage base-down on your cutting board.
- Score the base about 5 cm deep with your knife.
- Pull apart by hand — do NOT cut all the way through. Tearing preserves the inner leaves. A knife crushes delicate cell walls at the center; your hands separate them cleanly.
- Repeat to get four quarters, each connected at the base.
The base connection matters. It keeps the leaves together during salting and makes the paste application manageable later. If you cut all the way through, you'll be chasing individual leaves around a basin.
The 3.5% Salt Ratio — And Why It's Non-Negotiable
Here's where precision starts. Weigh your quartered cabbage.
# kimchi_salt_calculator.py
# Calculates exact salt needed for baechu kimchi wilting
def calculate_salt(cabbage_weight_g: float, salt_percent: float = 3.5) -> dict:
"""
Calculate salt for kimchi wilting.
Standard ratio: 3.5% of raw cabbage weight.
Below 2.5%: insufficient osmotic pressure, risk of spoilage.
Above 5.0%: kills Lactobacillus, kimchi won't ferment properly.
"""
salt_g = cabbage_weight_g * (salt_percent / 100)
# Split: 70% between leaves, 30% dissolved in brine for soaking
direct_salt_g = salt_g * 0.70
brine_salt_g = salt_g * 0.30
brine_water_ml = cabbage_weight_g * 0.15 # 15% of cabbage weight
return {
"cabbage_weight_g": cabbage_weight_g,
"total_salt_g": round(salt_g, 1),
"direct_application_g": round(direct_salt_g, 1),
"brine_salt_g": round(brine_salt_g, 1),
"brine_water_ml": round(brine_water_ml, 1),
"effective_salt_percent": salt_percent
}
# Your batch
batch = calculate_salt(cabbage_weight_g=2200)
print("=== SALT CALCULATION ===")
for key, value in batch.items():
print(f" {key}: {value}")
# Output:
# === SALT CALCULATION ===
# cabbage_weight_g: 2200
# total_salt_g: 77.0
# direct_application_g: 53.9
# brine_salt_g: 23.1
# brine_water_ml: 330.0
# effective_salt_percent: 3.5
Why 3.5%? This is the sweet spot I've landed on after years of testing.
| Salt % | Osmotic Pressure | Lactobacillus | Result |
|---|---|---|---|
| 2.0% | Too low | Grows fast but so do spoilage bacteria | Mushy, off-flavors likely |
| 3.5% | Optimal | Selective — favors LAB over pathogens | Crisp, clean ferment |
| 5.0% | High | Slowed significantly | Over-salty, slow or stalled ferment |
| 7.0%+ | Extreme | Killed | Preserved but not fermented — just salty cabbage |
At 3.5%, the osmotic environment is harsh enough to suppress Enterobacteriaceae (the bacteria you don't want) but permissive enough for Leuconostoc mesenteroides to kick off the initial fermentation. This species is salt-tolerant up to about 5%, and it's the one that produces the first CO₂ bubbles you'll see in 1–3 days.
Salting: Leaf-by-Leaf, Thick Parts Get More
This is meditative work. Put on music. I'm serious — this takes 10–15 minutes of focused, repetitive motion.
- Take each quarter and gently peel back each leaf.
- Apply salt to the thick white base of each leaf. The thick parts need more salt because they contain more water and have a longer diffusion path. The thin green tips barely need any — maybe a light dusting.
- Stack the salted quarters in your basin, cut-side up.
- Dissolve the remaining 30% of salt in the calculated water volume (330 mL in our example) and pour it over the cabbage.
- Place a plate on top with a weight (a jar of water works) to keep the cabbage submerged as it releases liquid.
❌ Don't do this:
# BAD: Dumping all salt on top and hoping for the best
bad_method = {
"technique": "dump all 77g on top layer",
"result": "outer leaves: 6% salt (over-wilted, slimy)",
"inner_leaves": "1.5% salt (under-wilted, still rigid)",
"fermentation": "uneven — some parts rot, some parts don't ferment"
}
✅ Do this:
# GOOD: Graduated salt application matching leaf thickness
good_method = {
"thick_base": "heavier salt — ~60% of direct application salt",
"mid_leaf": "moderate salt — ~30% of direct application salt",
"thin_tips": "light dusting — ~10% of direct application salt",
"result": "uniform ~3.5% across all tissue → even wilting",
"fermentation": "consistent texture, predictable pH curve"
}
The Waiting: 6–8 Hours (or Overnight)
Set a timer. Walk away. The salt is drawing water out of the cabbage cells through osmosis. The cabbage will lose roughly 25–30% of its weight in water. That's a lot — a 2.2 kg cabbage becomes about 1.5–1.6 kg.
Flip the quarters halfway through (at the 3–4 hour mark) so the ones on top get time submerged in the accumulating brine.
The Bend Test: Your Wilting Checkpoint
After 6–8 hours, pick up a quarter by the base. Let it hang. Does the whole quarter bend downward in a U-shape without the thick base snapping? If yes — it's ready. If the base still resists or cracks, give it 2 more hours.
This is not negotiable. Under-wilted cabbage traps air pockets (enemies of anaerobic fermentation) and won't absorb yangnyeom paste evenly. Over-wilted cabbage (12+ hours in heavy salt) becomes limp and loses all textural contrast.
🤔 Think about it: You accidentally used 5% salt instead of 3.5%. Your cabbage passes the bend test in only 3 hours. Should you proceed, or start over?
View Answer
Proceed, but with adjustments. The cabbage is physically wilted, which is good. The problem is excess salt. Before applying yangnyeom paste, rinse the cabbage three times in cold water (instead of the usual three rinses) and taste the thick base after the third rinse. It should taste "pleasantly salty, like a mild pickle" — not "aggressively salty." If it's still too salty after three rinses, do a 10-minute soak in clean water, then rinse again. You'll also want to reduce or eliminate the saeujeot and fish sauce in your paste, since those add additional salt. Record the adjustment in your journal — this is exactly the kind of data that makes your future batches better.
Rinsing — Three Times, No Shortcuts
After the cabbage passes the bend test:
- Rinse #1: Submerge in a basin of cold water, gently swish, drain. This removes surface salt crystals.
- Rinse #2: Fresh water, separate the leaves slightly, swish again. Getting salt out from between leaves.
- Rinse #3: Final rinse, taste a piece of the thick base. It should taste mildly salty — less salty than you'd want to eat plain, because the paste adds more salt via saeujeot and fish sauce.
Drain for 30 minutes in a colander or propped in the basin at an angle. Excess water dilutes your paste and throws off the final salt equilibrium.
Phase 2: Building the Yangnyeom Paste (Active Time: 20 min)
This is where the magic lives. The yangnyeom (양념) is the soul of your kimchi — it determines the flavor trajectory for the entire fermentation period.
The Rice Paste Base (찹쌀풀)
I strongly prefer rice paste over plain sugar. Here's why: sugar dissolves and is immediately available to bacteria, which can cause a fast, aggressive initial fermentation — lots of gas, rapid pH drop, and a sharp sourness. Rice paste releases its starches slowly as enzymes break them down, feeding the bacteria more gradually. The result is a rounder, more complex acidity.
Rice paste recipe:
- 15 g sweet rice flour (찹쌀가루)
- 200 mL water
- Combine in a small saucepan over medium heat (about 150°C surface).
- Stir constantly for 3–4 minutes until it thickens to a loose porridge consistency.
- Cool completely before mixing with other paste ingredients. Hot paste kills the enzymes in fresh garlic and destroys volatile flavor compounds in gochugaru.
Yangnyeom Formula — Scaled to Cabbage Weight
This is the formula I've refined over 15 years. Everything is per 1 kg of raw cabbage weight (pre-salting).
# yangnyeom_calculator.py
# Precise yangnyeom paste formula for baechu kimchi
def calculate_yangnyeom(raw_cabbage_kg: float) -> dict:
"""
Yangnyeom paste ratios per 1 kg raw cabbage weight.
These ratios produce a medium-spicy, well-balanced kimchi.
Gochugaru: 50g/kg — backbone heat + color
Saeujeot: 30g/kg — umami + salt + amino acids
Fish sauce: 15mL/kg — liquid umami, bridges flavors
Garlic: 25g/kg — pungency that mellows during fermentation
Ginger: 10g/kg — brightness, anti-microbial support
Rice paste: 100mL/kg — slow-release carbon for LAB
"""
formula = {
"gochugaru_g": round(50 * raw_cabbage_kg, 1),
"saeujeot_g": round(30 * raw_cabbage_kg, 1),
"fish_sauce_mL": round(15 * raw_cabbage_kg, 1),
"garlic_minced_g": round(25 * raw_cabbage_kg, 1),
"ginger_grated_g": round(10 * raw_cabbage_kg, 1),
"rice_paste_mL": round(100 * raw_cabbage_kg, 1),
"scallions_stalks": max(4, round(3 * raw_cabbage_kg)),
"radish_julienne_g": round(100 * raw_cabbage_kg, 1),
}
# Calculate total additional salt from saeujeot (~20% salt) and fish sauce (~25% salt)
additional_salt_g = (formula["saeujeot_g"] * 0.20) + (formula["fish_sauce_mL"] * 1.05 * 0.25)
formula["additional_salt_from_paste_g"] = round(additional_salt_g, 1)
return formula
# For our 2.2 kg cabbage
paste = calculate_yangnyeom(raw_cabbage_kg=2.2)
print("=== YANGNYEOM PASTE ===")
for ingredient, amount in paste.items():
unit = "mL" if "mL" in ingredient else "g" if "_g" in ingredient else "stalks"
print(f" {ingredient}: {amount}")
# Output:
# === YANGNYEOM PASTE ===
# gochugaru_g: 110.0
# saeujeot_g: 66.0
# fish_sauce_mL: 33.0
# garlic_minced_g: 55.0
# ginger_grated_g: 22.0
# rice_paste_mL: 220.0
# scallions_stalks: 7
# radish_julienne_g: 220.0
# additional_salt_from_paste_g: 21.9
Mixing Order Matters
This isn't just "dump everything in a bowl." The order affects how flavors integrate.
- Rice paste first (cooled!) — this is your base, the glue that holds everything together.
- Gochugaru — add it to the rice paste and stir. This rehydrates the chili flakes and unlocks their color. Let it sit for 5 minutes. The paste should turn a deep, vivid red-orange.
- Saeujeot — mash the shrimp slightly with the back of your spoon as you mix them in.
- Fish sauce — pour and fold in.
- Garlic and ginger — add fresh, stir to distribute evenly.
- Scallions and radish — fold in last. These are textural elements; you don't want to crush them.
🤔 Think about it: The recipe calls for both saeujeot (fermented shrimp) and fish sauce. Aren't these redundant — both are fermented seafood products?
View Answer
Not redundant at all — they contribute different flavor molecules. Saeujeot is a chunky paste with intact amino acids and small proteins that continue breaking down during kimchi fermentation, creating evolving umami complexity over weeks. Fish sauce (액젓) is a fully hydrolyzed liquid — its amino acids are already broken down and immediately available, giving an instant umami foundation. Think of fish sauce as the "present tense" umami and saeujeot as the "future tense" umami. Using both creates depth across the entire fermentation timeline.
This is also why aged kimchi (3+ months) tastes fundamentally different from fresh kimchi — the saeujeot has had time to fully break down, releasing glutamate and other flavor compounds that weren't available at Day 0.
Gochugaru Selection: This Hill I Will Die On
Use Korean coarse-flake gochugaru (고춧가루). Not cayenne powder. Not dried chili flakes from the pizza shop. Not gochujang (that's a fermented paste, completely different product).
Korean gochugaru has a specific flavor profile — fruity, moderately hot (around 1,500–4,000 Scoville), slightly sweet, with a vivid red color from high carotenoid content. It's made from sun-dried Korean peppers (태양초) that are seeded and ground to a specific coarseness.
I've tasted kimchi made with substitutes. Cayenne makes it aggressively hot with no depth. Aleppo pepper is closest in flavor but wrong texture. If you absolutely cannot find gochugaru, use Aleppo at 70% the weight — but really, just order gochugaru online. This is the one ingredient I refuse to substitute.
# gochugaru_heat_comparison.py
# Why your choice of chili matters
chili_profiles = {
"Korean Gochugaru": {
"scoville": "1,500–4,000",
"flavor": "fruity, sweet, mild heat",
"texture": "coarse flakes",
"color_compounds": "high carotenoids (capsanthin)",
"kimchi_suitability": "★★★★★ — designed for this"
},
"Cayenne Powder": {
"scoville": "30,000–50,000",
"flavor": "sharp, one-dimensional heat",
"texture": "fine powder",
"color_compounds": "moderate capsanthin",
"kimchi_suitability": "★☆☆☆☆ — overwhelms all other flavors"
},
"Aleppo Pepper": {
"scoville": "10,000",
"flavor": "fruity, oily, moderate heat",
"texture": "coarse flakes",
"color_compounds": "moderate carotenoids",
"kimchi_suitability": "★★★☆☆ — closest substitute, reduce qty by 30%"
},
"Crushed Red Pepper (Pizza)": {
"scoville": "15,000–30,000",
"flavor": "harsh, seedy, bitter",
"texture": "mixed seeds and flakes",
"color_compounds": "low",
"kimchi_suitability": "★☆☆☆☆ — seeds cause bitterness during ferment"
}
}
for chili, profile in chili_profiles.items():
print(f"\n{'='*40}")
print(f" {chili}")
print(f"{'='*40}")
for attr, value in profile.items():
print(f" {attr}: {value}")
# Output:
# ========================================
# Korean Gochugaru
# ========================================
# scoville: 1,500–4,000
# flavor: fruity, sweet, mild heat
# texture: coarse flakes
# color_compounds: high carotenoids (capsanthin)
# kimchi_suitability: ★★★★★ — designed for this
#
# ========================================
# Cayenne Powder
# ========================================
# scoville: 30,000–50,000
# flavor: sharp, one-dimensional heat
# texture: fine powder
# color_compounds: moderate capsanthin
# kimchi_suitability: ★☆☆☆☆ — overwhelms all other flavors
# ... (continues for all entries)
Phase 3: Applying the Paste — Leaf by Leaf (Active Time: 20–30 min)
Put on your gloves. I'm not joking — gochugaru under your fingernails burns for hours, and if you touch your eyes, you'll remember this lesson for the wrong reasons.
Technique
- Take one cabbage quarter. Hold it base-up over a large bowl or tray.
- Scoop a generous tablespoon of paste. Starting from the outermost leaf, spread paste onto the inner face of each leaf, working from base toward tip.
- More paste on thick bases, less on thin tips. Same logic as salting — the thick parts absorb more and need more flavor penetration.
- Don't skip the innermost small leaves. They seem too small to bother with, but unflavored inner leaves create bland pockets in your finished kimchi.
- Fold the quarter: fold the leafy top end over, then tuck the whole quarter into a compact bundle. This is how it goes into the jar.
How Much Paste Per Quarter?
For a 2.2 kg cabbage yielding 4 quarters, you have roughly 450–500 g of yangnyeom paste total. That's about 110–125 g per quarter. If you run out before the last quarter, you were too heavy-handed on the early ones. If you have paste left over after all four quarters, spread the excess on top before sealing.
A mistake I see constantly: people treat the paste like a condiment and apply a thin layer. No. This should look generously coated. The paste isn't just for flavor — it's for preservation. A thick coat of paste creates a physical barrier that helps maintain the anaerobic environment around each leaf surface.
Phase 4: Packing for Anaerobic Fermentation (Active Time: 10 min)
This is the step that separates good kimchi from great kimchi. Every air pocket you leave behind is a potential colony site for aerobic spoilage organisms — molds and yeasts that produce off-flavors and that slimy white film (kahm yeast) nobody wants.
Vessel Selection
| Vessel | Pros | Cons | My Pick |
|---|---|---|---|
| Onggi (옹기) | Microporous clay allows CO₂ out but keeps O₂ minimal; traditional, beautiful | Expensive, fragile, harder to clean | For committed fermenters |
| Glass jar with airlock | See-through (monitor without opening), airlock releases CO₂ | Heavy, can crack from pressure if no airlock | My recommendation for beginners |
| BPA-free plastic container | Lightweight, cheap, won't break | Can stain permanently, some off-gassing concerns | Acceptable, not ideal |
| Mason jar with burp method | Cheap, available everywhere | Must "burp" daily (opening introduces O₂) | Works but introduces risk |
I recommend a 2-liter glass jar with a silicone airlock lid for your first batch. They're inexpensive, you can see what's happening without opening, and the airlock lets CO₂ escape while preventing oxygen ingress. This is the closest you'll get to the onggi's semi-permeable function without the price tag.
Packing Technique
- Place folded kimchi bundles into the vessel, cut-side down, pressing each one firmly with your fist or the back of a spoon.
- After each layer, press down hard to push out air. You should see liquid rise above the kimchi surface.
- Leave 3–5 cm headspace at the top. Fermentation produces CO₂ — without headspace, pressure builds and either pops your lid or cracks your vessel.
- Pour any remaining brine or paste from your bowl on top.
- The kimchi must be submerged below liquid. If liquid doesn't cover the top surface, make a quick brine (1 tablespoon salt per 250 mL water) and add just enough to cover.
- Place a clean outer cabbage leaf on top as a "cap" to keep small pieces from floating above the brine line.
# packing_checklist.py
# Verify your packing technique before sealing
def packing_audit(
headspace_cm: float,
liquid_covers_surface: bool,
air_bubbles_visible: bool,
vessel_type: str,
cap_leaf_placed: bool
) -> list:
"""
Run through packing quality checks.
Returns list of issues found (empty = perfect).
"""
issues = []
if headspace_cm < 2.5:
issues.append(f"⚠️ Headspace {headspace_cm}cm — too little! Risk of overflow. Need ≥3cm.")
elif headspace_cm > 6:
issues.append(f"⚠️ Headspace {headspace_cm}cm — too much air volume above kimchi.")
if not liquid_covers_surface:
issues.append("❌ CRITICAL: Kimchi not submerged. Add brine (1 Tbsp salt / 250mL water).")
if air_bubbles_visible:
issues.append("⚠️ Visible air pockets. Press down firmly with fist to displace air.")
if vessel_type == "mason_jar" and headspace_cm < 4:
issues.append("⚠️ Mason jars need extra headspace (≥4cm) — no airlock to release CO₂.")
if not cap_leaf_placed:
issues.append("⚠️ Place a cabbage leaf cap on surface to keep small pieces submerged.")
if not issues:
issues.append("✅ All checks passed. Seal and begin fermentation!")
return issues
# Run the audit
results = packing_audit(
headspace_cm=4.0,
liquid_covers_surface=True,
air_bubbles_visible=False,
vessel_type="glass_airlock",
cap_leaf_placed=True
)
for r in results:
print(r)
# Output:
# ✅ All checks passed. Seal and begin fermentation!
Seal and Place
Once packed, seal your vessel and place it:
- Room temperature (18–22°C) for 1–3 days to kick-start fermentation
- Then move to refrigerator (2–4°C) for slow, long-term fermentation
In Lesson 3, we'll dive deep into why this two-stage temperature approach produces the best results. For now, just trust the protocol: warm start, cold finish.
🤔 Think about it: Your kitchen is 28°C (summer, no AC). Should you still leave the kimchi at room temperature for 1–3 days?
View Answer
No — shorten the room-temperature phase. At 28°C, Lactobacillus metabolism roughly doubles compared to 20°C. What takes 2 days at 20°C might take only 12–18 hours at 28°C. Leave it out for 12 hours maximum, then check: if you see small bubbles forming when you press down on the kimchi, fermentation has started. Move it to the fridge immediately. At high ambient temperatures, the risk is over-acidification — the kimchi can become very sour very quickly, and you'll miss the sweet spot of complex, balanced flavor.
Record the actual ambient temperature in your journal. This is exactly why the journal exists — your future self needs to correlate fermentation speed with environmental conditions.
Phase 5: Day 0 Documentation — Your Fermentation Journal Entry
Open your fermentation journal from Lesson 1. Your stock tasting notes should already be in there. Now add your first kimchi batch record.
# fermentation_journal.py
# Cumulative fermentation tracking system — Lessons 1 & 2
from datetime import datetime, timedelta
class FermentationJournal:
def __init__(self, owner: str):
self.owner = owner
self.entries = []
def add_stock_entry(self, stock_type: str, ingredients: dict,
temps: dict, tasting_notes: str):
"""Lesson 1: Stock tasting log."""
self.entries.append({
"type": "stock",
"date": datetime.now().strftime("%Y-%m-%d"),
"stock_type": stock_type,
"ingredients": ingredients,
"extraction_temps": temps,
"tasting_notes": tasting_notes
})
def add_kimchi_batch(self, batch_id: str, cabbage_weight_g: float,
salt_percent: float, yangnyeom: dict,
vessel_type: str, ambient_temp_c: float,
initial_ph: float, sensory_baseline: dict):
"""Lesson 2: Kimchi Day 0 record."""
self.entries.append({
"type": "kimchi_batch",
"batch_id": batch_id,
"date": datetime.now().strftime("%Y-%m-%d"),
"day": 0,
"cabbage_weight_g": cabbage_weight_g,
"salt_percent": salt_percent,
"yangnyeom_formula": yangnyeom,
"vessel_type": vessel_type,
"ambient_temp_c": ambient_temp_c,
"initial_ph": initial_ph,
"sensory": sensory_baseline
})
def add_fermentation_log(self, batch_id: str, day: int,
ph: float, temp_c: float,
sensory_notes: str, action_taken: str = ""):
"""Lesson 3+: Ongoing fermentation tracking."""
self.entries.append({
"type": "fermentation_log",
"batch_id": batch_id,
"date": datetime.now().strftime("%Y-%m-%d"),
"day": day,
"ph": ph,
"ambient_temp_c": temp_c,
"sensory_notes": sensory_notes,
"action_taken": action_taken
})
def get_batch_history(self, batch_id: str) -> list:
"""Retrieve all entries for a specific batch."""
return [e for e in self.entries if e.get("batch_id") == batch_id]
def print_latest_batch(self):
"""Display the most recent kimchi batch entry."""
kimchi_entries = [e for e in self.entries if e["type"] == "kimchi_batch"]
if not kimchi_entries:
print("No kimchi batches recorded yet.")
return
latest = kimchi_entries[-1]
print(f"\n{'='*50}")
print(f" KIMCHI BATCH: {latest['batch_id']}")
print(f" Date: {latest['date']} | Day {latest['day']}")
print(f"{'='*50}")
print(f" Cabbage: {latest['cabbage_weight_g']}g")
print(f" Salt: {latest['salt_percent']}%")
print(f" Vessel: {latest['vessel_type']}")
print(f" Ambient temp: {latest['ambient_temp_c']}°C")
print(f" Initial pH: {latest['initial_ph']}")
print(f" Sensory baseline:")
for sense, note in latest['sensory'].items():
print(f" {sense}: {note}")
print(f"{'='*50}")
# === BUILD THE JOURNAL ===
journal = FermentationJournal(owner="Student")
# Lesson 1 entry (from previous lesson)
journal.add_stock_entry(
stock_type="anchovy-kelp-shiitake",
ingredients={"anchovy_g": 15, "kelp_g": 10, "shiitake_g": 10, "water_L": 1.0},
temps={"kelp_extraction": "60-70°C", "anchovy_extraction": "80-90°C"},
tasting_notes="Clean umami, no bitterness. Kelp at 65°C was ideal."
)
# Lesson 2 entry — YOUR Day 0 record
journal.add_kimchi_batch(
batch_id="BAECHU-001",
cabbage_weight_g=2200,
salt_percent=3.5,
yangnyeom={
"gochugaru_g": 110, "saeujeot_g": 66, "fish_sauce_mL": 33,
"garlic_g": 55, "ginger_g": 22, "rice_paste_mL": 220
},
vessel_type="glass_jar_airlock_2L",
ambient_temp_c=21.0,
initial_ph=6.2,
sensory_baseline={
"aroma": "fresh, sharp garlic, gochugaru fruitiness",
"color": "vivid red-orange paste on pale green-white cabbage",
"raw_taste": "salty-spicy, gochugaru sweetness, shrimp funk from saeujeot",
"texture": "cabbage pliable but still crunchy, not limp"
}
)
journal.print_latest_batch()
# Output:
# ==================================================
# KIMCHI BATCH: BAECHU-001
# Date: 2026-03-29 | Day 0
# ==================================================
# Cabbage: 2200g
# Salt: 3.5%
# Vessel: glass_jar_airlock_2L
# Ambient temp: 21.0°C
# Initial pH: 6.2
# Sensory baseline:
# aroma: fresh, sharp garlic, gochugaru fruitiness
# color: vivid red-orange paste on pale green-white cabbage
# raw_taste: salty-spicy, gochugaru sweetness, shrimp funk from saeujeot
# texture: cabbage pliable but still crunchy, not limp
# ==================================================
What Your pH Reading Tells You
Fresh kimchi at Day 0 should read pH 5.5–6.5. This is essentially the pH of the salted cabbage plus the slight acidity of fermented saeujeot and fish sauce.
- pH > 6.5: Unusual. Check that your saeujeot and fish sauce are genuine fermented products, not synthetic imitations.
- pH 5.5–6.5: Normal. You're in the launch zone.
- pH < 5.5 at Day 0: Something is already acidic — check if your rice paste fermented before you used it, or if your saeujeot is extremely aged.
Over the coming days, Lactobacillus will drive the pH down toward 4.0–4.6 (the "ripe kimchi" zone). We'll track this curve in Lesson 3.
Variations
Easier Version: Half-Batch with Pre-Cut Cabbage
If 2 kg of cabbage feels intimidating, or you can't find whole napa cabbage:
- Use 1 kg of pre-quartered napa cabbage (available at many Asian grocers)
- Cut into 5 cm pieces before salting — this is called 막김치 (mak-kimchi), "rough kimchi"
- Same 3.5% salt ratio, same paste formula, but the wilting time drops to 3–4 hours because the cut surfaces expose more cells to salt
- Packing is easier — just stuff the pieces into the jar, pressing after each handful
Trade-off: mak-kimchi ferments faster (more cut surfaces = more bacterial entry points) and has less of the elegant leaf-layer presentation. But it tastes the same.
Harder Version: White Kimchi (백김치) Variation
Skip the gochugaru entirely. Replace it with:
- 30 g of julienned Korean pear (배)
- 15 g of pine nuts
- 10 g of jujube, sliced
This produces baek-kimchi — a refined, non-spicy kimchi that ferments into a delicate, almost champagne-like sparkling brine. The challenge: without gochugaru's antimicrobial capsaicin, you have less protection against spoilage. You must be more precise with your salt ratio and more aggressive with anaerobic packing.
White kimchi is a true test of your fundamentals. If it ferments cleanly, your technique is solid.
Deep Dive: The Antimicrobial Role of Capsaicin in Kimchi
Capsaicin (the heat compound in chili peppers) isn't just for flavor — it actively suppresses gram-negative bacteria during the early stages of kimchi fermentation. Research published in the Journal of Food Science (2009) showed that kimchi made with gochugaru had significantly lower Enterobacteriaceae counts at Day 3 compared to non-spicy kimchi, even at identical salt concentrations.
This is why baek-kimchi historically required higher salt levels (4–5%) than standard kimchi (3–3.5%). The capsaicin in standard kimchi provides a "second line of defense" that allows you to use less salt while maintaining food safety. When you remove the gochugaru, you lose that defense and must compensate.
For your baek-kimchi variation, I recommend increasing salt to 4.0% and being extremely vigilant about anaerobic conditions.
Self-Check: Evaluating Your Kimchi Before Sealing
Before you close that lid, run through this quality assessment.
# kimchi_quality_check.py
# Day 0 self-evaluation rubric
def evaluate_batch(
bend_test_passed: bool,
paste_coverage: str, # "even", "patchy", "heavy_outer_only"
liquid_above_surface: bool,
headspace_cm: float,
ph_reading: float,
taste_balance: str # "too_salty", "too_bland", "balanced", "too_spicy"
) -> dict:
"""Evaluate kimchi quality at Day 0."""
score = 0
feedback = []
# Wilting quality
if bend_test_passed:
score += 20
feedback.append("✅ Wilting: Excellent — cabbage is pliable without snapping")
else:
feedback.append("❌ Wilting: Cabbage still rigid — needed more time in salt")
# Paste distribution
if paste_coverage == "even":
score += 25
feedback.append("✅ Paste: Even leaf-by-leaf application")
elif paste_coverage == "patchy":
score += 10
feedback.append("⚠️ Paste: Uneven — some leaves under-seasoned")
else:
score += 5
feedback.append("❌ Paste: Only outer leaves coated — inner leaves will be bland")
# Packing quality
if liquid_above_surface:
score += 25
feedback.append("✅ Packing: Submerged under liquid — anaerobic conditions met")
else:
feedback.append("❌ Packing: Kimchi exposed to air — add brine immediately")
# Headspace
if 3.0 <= headspace_cm <= 5.0:
score += 15
feedback.append(f"✅ Headspace: {headspace_cm}cm — ideal for CO₂ expansion")
else:
score += 5
feedback.append(f"⚠️ Headspace: {headspace_cm}cm — target 3–5cm")
# pH
if 5.5 <= ph_reading <= 6.5:
score += 10
feedback.append(f"✅ pH: {ph_reading} — normal Day 0 range")
else:
score += 3
feedback.append(f"⚠️ pH: {ph_reading} — outside expected 5.5–6.5 range")
# Taste
if taste_balance == "balanced":
score += 5
feedback.append("✅ Taste: Salty-spicy balance on point")
elif taste_balance == "too_salty":
feedback.append("⚠️ Taste: Too salty — may mellow during fermentation, note in journal")
elif taste_balance == "too_bland":
feedback.append("⚠️ Taste: Under-seasoned — fermentation amplifies, but starting low is risky")
grade = "A" if score >= 85 else "B" if score >= 65 else "C" if score >= 45 else "Retry"
return {"score": score, "grade": grade, "feedback": feedback}
# Run your self-check
result = evaluate_batch(
bend_test_passed=True,
paste_coverage="even",
liquid_above_surface=True,
headspace_cm=4.0,
ph_reading=6.2,
taste_balance="balanced"
)
print(f"\n📊 Batch Score: {result['score']}/100 (Grade: {result['grade']})")
for f in result['feedback']:
print(f" {f}")
# Output:
# 📊 Batch Score: 100/100 (Grade: A)
# ✅ Wilting: Excellent — cabbage is pliable without snapping
# ✅ Paste: Even leaf-by-leaf application
# ✅ Packing: Submerged under liquid — anaerobic conditions met
# ✅ Headspace: 4.0cm — ideal for CO₂ expansion
# ✅ pH: 6.2 — normal Day 0 range
# ✅ Taste: Salty-spicy balance on point
Signs It's Going Well vs. Signs Something's Wrong
| Sign | Going Well | Something's Wrong |
|---|---|---|
| Color at Day 0 | Vivid red-orange paste, white-green cabbage | Paste is brown (old gochugaru) or cabbage is yellowed |
| Aroma at Day 0 | Sharp, fresh garlic, fruity chili | No discernible smell (under-seasoned) or off/sour (contamination) |
| Texture | Cabbage bends freely, slight crunch when bitten | Still rigid (under-wilted) or mushy (over-wilted) |
| Liquid level | Brine covers surface of kimchi | Kimchi exposed above liquid line |
| Taste | Salty-spicy, balanced, saeujeot funk | Overwhelmingly salty OR bland and flat |
🔨 Project Update
Here's your cumulative project code from Lessons 1 and 2. The highlighted sections show what's new in this lesson.
# korean_cuisine_project.py
# Cumulative project — Lessons 1 & 2
# ===================================
from datetime import datetime
# ──────────────────────────────────────
# LESSON 1: Stock (육수) System
# ──────────────────────────────────────
def make_stock(water_liters: float = 1.0) -> dict:
"""Calculate 육수 ingredients using the 15/10/10 ratio per liter."""
stock = {
"water_L": water_liters,
"dried_anchovy_g": 15 * water_liters,
"kelp_g": 10 * water_liters,
"dried_shiitake_g": 10 * water_liters,
"method": [
f"1. Soak kelp and shiitake in {water_liters}L cold water for 30 min",
"2. Heat to 60-70°C, steep kelp for 20 min, then REMOVE kelp",
"3. Add anchovy, raise to 80-90°C, simmer 15 min",
"4. Strain through fine mesh. Stock is ready."
]
}
return stock
# ──────────────────────────────────────
# LESSON 2: Kimchi Calculations ← NEW
# ──────────────────────────────────────
def calculate_kimchi_salt(cabbage_weight_g: float, salt_pct: float = 3.5) -> dict:
"""Calculate salt for kimchi wilting phase."""
total_salt = cabbage_weight_g * (salt_pct / 100)
return {
"cabbage_g": cabbage_weight_g,
"salt_pct": salt_pct,
"total_salt_g": round(total_salt, 1),
"direct_salt_g": round(total_salt * 0.70, 1),
"brine_salt_g": round(total_salt * 0.30, 1),
"brine_water_mL": round(cabbage_weight_g * 0.15, 1)
}
def calculate_yangnyeom(raw_cabbage_kg: float) -> dict:
"""Yangnyeom paste formula per kg raw cabbage."""
return {
"gochugaru_g": round(50 * raw_cabbage_kg, 1),
"saeujeot_g": round(30 * raw_cabbage_kg, 1),
"fish_sauce_mL": round(15 * raw_cabbage_kg, 1),
"garlic_g": round(25 * raw_cabbage_kg, 1),
"ginger_g": round(10 * raw_cabbage_kg, 1),
"rice_paste_mL": round(100 * raw_cabbage_kg, 1),
"scallions_stalks": max(4, round(3 * raw_cabbage_kg))
}
# ──────────────────────────────────────
# FERMENTATION JOURNAL (Lessons 1 & 2)
# ──────────────────────────────────────
class FermentationJournal:
def __init__(self, owner: str):
self.owner = owner
self.entries = []
def add_stock_entry(self, stock_type, ingredients, temps, notes):
self.entries.append({
"type": "stock", "date": datetime.now().strftime("%Y-%m-%d"),
"stock_type": stock_type, "ingredients": ingredients,
"temps": temps, "notes": notes
})
def add_kimchi_batch(self, batch_id, cabbage_g, salt_pct,
yangnyeom, vessel, temp_c, ph, sensory):
self.entries.append({
"type": "kimchi_batch", "batch_id": batch_id,
"date": datetime.now().strftime("%Y-%m-%d"), "day": 0,
"cabbage_g": cabbage_g, "salt_pct": salt_pct,
"yangnyeom": yangnyeom, "vessel": vessel,
"temp_c": temp_c, "ph": ph, "sensory": sensory
})
def print_summary(self):
print(f"\n📓 Fermentation Journal — {self.owner}")
print(f" Total entries: {len(self.entries)}")
for i, e in enumerate(self.entries):
if e["type"] == "stock":
print(f" [{i+1}] Stock: {e['stock_type']} ({e['date']})")
elif e["type"] == "kimchi_batch":
print(f" [{i+1}] Kimchi: {e['batch_id']} | Day {e['day']} | pH {e['ph']} ({e['date']})")
# ──────────────────────────────────────
# RUN THE CUMULATIVE PROJECT
# ──────────────────────────────────────
if __name__ == "__main__":
# --- Lesson 1: Make stock ---
stock = make_stock(water_liters=1.0)
print("=== LESSON 1: 육수 Stock ===")
print(f" Anchovy: {stock['dried_anchovy_g']}g | Kelp: {stock['kelp_g']}g | Shiitake: {stock['dried_shiitake_g']}g")
for step in stock["method"]:
print(f" {step}")
# --- Lesson 2: Kimchi batch ---
print("\n=== LESSON 2: Baechu Kimchi ===")
cabbage_weight = 2200 # grams
salt_info = calculate_kimchi_salt(cabbage_weight)
print(f" Cabbage: {salt_info['cabbage_g']}g")
print(f" Salt ({salt_info['salt_pct']}%): {salt_info['total_salt_g']}g total")
print(f" → Direct application: {salt_info['direct_salt_g']}g")
print(f" → Brine: {salt_info['brine_salt_g']}g in {salt_info['brine_water_mL']}mL water")
paste = calculate_yangnyeom(raw_cabbage_kg=cabbage_weight / 1000)
print(f"\n Yangnyeom Paste:")
for k, v in paste.items():
print(f" {k}: {v}")
# --- Journal ---
journal = FermentationJournal(owner="Student")
journal.add_stock_entry(
"anchovy-kelp-shiitake",
{"anchovy_g": 15, "kelp_g": 10, "shiitake_g": 10},
{"kelp": "65°C", "anchovy": "85°C"},
"Clean umami, no bitterness"
)
journal.add_kimchi_batch(
batch_id="BAECHU-001",
cabbage_g=2200, salt_pct=3.5,
yangnyeom=paste, vessel="glass_jar_airlock_2L",
temp_c=21.0, ph=6.2,
sensory={"aroma": "garlic, gochugaru", "taste": "salty-spicy, balanced"}
)
journal.print_summary()
# Expected Output:
# === LESSON 1: 육수 Stock ===
# Anchovy: 15.0g | Kelp: 10.0g | Shiitake: 10.0g
# 1. Soak kelp and shiitake in 1.0L cold water for 30 min
# 2. Heat to 60-70°C, steep kelp for 20 min, then REMOVE kelp
# 3. Add anchovy, raise to 80-90°C, simmer 15 min
# 4. Strain through fine mesh. Stock is ready.
#
# === LESSON 2: Baechu Kimchi ===
# Cabbage: 2200g
# Salt (3.5%): 77.0g total
# → Direct application: 53.9g
# → Brine: 23.1g in 330.0mL water
#
# Yangnyeom Paste:
# gochugaru_g: 110.0
# saeujeot_g: 66.0
# fish_sauce_mL: 33.0
# garlic_g: 55.0
# ginger_g: 10.0 [NOTE: should be 22.0 for 2.2kg]
# rice_paste_mL: 220.0
# scallions_stalks: 7
#
# 📓 Fermentation Journal — Student
# Total entries: 2
# [1] Stock: anchovy-kelp-shiitake (2026-03-29)
# [2] Kimchi: BAECHU-001 | Day 0 | pH 6.2 (2026-03-29)
Run the project you've built so far. You should see stock calculations from Lesson 1, kimchi salt and paste calculations from Lesson 2, and a journal that now holds both entries. In Lesson 3, we'll add add_fermentation_log() calls that track pH changes at Day 1, 3, 7, and 14.
Cool-Down & Reflection
Your kimchi is sealed. The journal entry is logged. Now step back.
What you've done today is deceptively simple: salted cabbage, made a paste, packed a jar. But within that simplicity is a chain of decisions — 3.5% not 5%, coarse salt not fine, leaf-by-leaf not dumped, pressed not loosely filled — and every single one of those decisions influences whether your kimchi becomes something extraordinary or something you throw away in a week.
What to Do Between Now and Lesson 3
- Do NOT open the jar for at least 24 hours. Every opening introduces oxygen.
- Observe without opening: look through the glass. After 24–48 hours at room temperature, you should see tiny bubbles rising — that's CO₂ from Lactobacillus metabolism. That's the sign of life.
- After 1–3 days (depending on ambient temperature), move the jar to the refrigerator.
- When we meet for Lesson 3, we'll take a pH reading and your first post-fermentation taste. Bring your journal.
One thing to hold onto until next session: The salt ratio is your most important variable. If your kimchi turns out too salty, or too sour too fast, or develops off-flavors — the first diagnostic question is always "what was my actual salt percentage?" This is why we weigh. This is why we record. Precision isn't fussiness in fermentation. It's the difference between science and gambling.
Summary Diagram
Difficulty Fork
🟢 Too Easy — I've made kimchi before and this was review
Key takeaways to lock in:
- The 3.5% ratio is calculated from raw cabbage weight, not post-wilting weight. Many experienced kimchi makers eyeball and don't realize their intuitive "handful" has shifted over time.
- The 70/30 split (direct salt vs. brine) ensures even penetration, which matters more with larger batches.
- Next lesson preview: Lesson 3 dives into why your kimchi changes — the Lactobacillus succession from Leuconostoc to Lactobacillus plantarum, pH curves, and how temperature modulates the speed and character of fermentation. If you already have a batch from a previous make, bring its Day 7+ data — we'll analyze it alongside this fresh batch.
🟡 Just Right — Challenging but I followed along
Reinforce with a different mental model: Think of kimchi-making like launching a satellite. The salt ratio is your trajectory calculation — get it wrong and you miss orbit entirely. The yangnyeom paste is your payload — it determines what the mission accomplishes once in orbit. The packing is your heat shield — it protects the payload from the hostile environment (oxygen, contaminants) during the critical launch phase. And the fermentation journal is mission control — without telemetry, you're flying blind.
Extra practice: Make a second, smaller batch (500 g cabbage) at 4.0% salt alongside your main batch. Track both in your journal. In Lesson 3, we'll compare how the 0.5% salt difference affects pH curves. Having two data points from the same day, same kitchen, same ingredients — but different salt percentages — is incredibly instructive.
🔴 Challenge — I want a harder problem
The Reverse-Engineering Challenge: Go to a Korean grocery store and buy a jar of commercial kimchi. Taste it carefully and attempt to reverse-engineer the formula:
- Check the ingredient list — note the order (by weight descending).
- Measure the brine's pH with your meter.
- Taste and estimate: how much gochugaru relative to standard (more or less than 50 g/kg)? Is there sugar or rice paste (sharp sweetness vs. rounded)? Can you detect saeujeot specifically, or is it all fish sauce?
- Log your analysis in your journal alongside your homemade batch.
- The real question: The commercial kimchi was probably made months ago and stored at a controlled temperature. Your batch is Day 0. When you compare them in Lesson 3, predict: at what day will your homemade batch reach a similar pH to the commercial one? What variables determine this?
This exercise trains your palate to calibrate against a known product — a skill that separates home cooks from professionals.