中文 English

Docker Container Crash Loop After v3.7.0? You're Missing One Word: serve

Published: 2026-07-02
Docker Siyuan CLI Breaking Change docker-compose Troubleshooting AI Agent

TL;DR

After upgrading Siyuan (SiYuan Note) from v3.6.x to v3.7.0, the Docker container entered an infinite restart loop. docker logs showed Error: unknown flag: --accessAuthCode. The root cause: v3.7.0 introduced a CLI subcommand architecture. The old top-level flags now require a serve subcommand. The fix is adding one word — 'serve' — to the beginning of the command array in docker-compose.yml. Seven characters. From crash loop to running.

This article covers the complete troubleshooting process, one-command fix scripts for Windows 11, Ubuntu 26.04, and macOS 26, plus both manual and AI Agent approaches. All scripts use only Docker CLI and SSH — no third-party services required.

AI-generated cover: Docker container restart loop troubleshooting and fix.

Figure 1: AI-generated cover. Sometimes a container crash loop needs only a one-line fix.

1. Background: Siyuan and Docker Deployment

SiYuan Note is an open-source personal knowledge management system with Markdown editing, bidirectional linking, data sync, and Docker support. Many users run it in Docker on a home server or VPS to access their notes from any device via a browser.

A typical Docker Compose configuration looks like this:

version: "3.9"
services:
  main:
    image: b3log/siyuan:v3.7.0
    command: ['--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']
    ports:
      - 6806:6806
    volumes:
      - /docker/siyuan_data:/siyuan/workspace
    restart: always
    environment:
      - PUID=1001
      - PGID=1002
      - SIYUAN_LANG=zh-CN

This worked perfectly in v3.6.x and earlier. However, v3.7.0, released on June 30, 2026, is a major update that brings a redesigned UI, a kernel plugin system, and — critically — a CLI subcommand architecture. That last one is what broke many Docker deployments.

What v3.7.0 brought

According to the official release notes, v3.7.0 is a comprehensive overhaul:

Item four — the CLI — is our “culprit.” It brings powerful automation capabilities but also introduces a breaking change.

Siyuan v3.7.0 architecture overview.

Figure 2: Siyuan Docker deployment architecture. Portainer manages the stack; entrypoint.sh launches kernel inside the container; kernel needs the serve subcommand to start the HTTP server.

2. Symptoms: Infinite Restart Loop

After upgrading the Siyuan image from v3.6.1 to v3.7.0 in Portainer and clicking “Re-deploy,” the container status looked wrong:

$ docker ps --filter name=siyuan
NAMES          STATUS
siyuan-qiniu   Restarting (1) 8 seconds ago

The container cycled endlessly through start → exit (code 1) → Docker auto-restart → exit again. The port mapping existed but the service was unreachable. The browser just spun.

docker ps showing container restart loop.

Figure 3: Real terminal screenshot — docker ps shows the Siyuan container in Restarting state.

Understanding the Restart Loop with an Analogy

Think of it like a car that won’t start:

You swapped in a new engine (upgraded the image), but the ignition sequence changed. Every time you turn the key, the engine coughs once and dies. The onboard computer auto-retries (restart: always), so you hear “cough-cough-cough” on repeat. To fix it, don’t just pull the battery — read the error codes. For containers, error codes are docker logs.

3. Analysis: docker logs Is Your First Clue

When a container misbehaves, the first instinct should not be to restart, roll back, or reinstall. It should be: read the logs.

$ docker logs --tail 40 siyuan-qiniu
Error: unknown flag: --accessAuthCode
Usage:
  kernel [command]

Available Commands:
  asset       Manage assets
  attr        Manage block attributes
  block       Block operations
  bookmark    Manage bookmarks
  completion  Generate the autocompletion script
  serve       Start kernel HTTP server
  export      Export documents
  ...
Flags:
      --dry-run            dry run mode
  -h, --help               help for kernel
  -w, --workspace string   workspace path

Real error log from docker logs.

Figure 4: Real screenshot — docker logs showing the kernel CLI error with available subcommands.

Siyuan v3.7.0 lock screen login page.

Figure 5: Real screenshot — Siyuan v3.7.0 lock screen after the fix. Enter your accessAuthCode to access your notes.

The log tells us clearly: --accessAuthCode is an unknown flag. And kernel lists all the subcommands it recognizes — serve, asset, block, etc. — with serve described as “Start kernel HTTP server.”

This output is a giant hint: kernel expects the first argument to be a subcommand, not a --workspace or --accessAuthCode flag.

The Troubleshooting Chain

  1. docker ps -a → container is Restarting
  2. docker logsunknown flag: --accessAuthCode
  3. docker inspect → confirm compose config path and command field
  4. Read official README → v3.7.0 requires explicit serve subcommand
  5. Root cause identified → command is missing serve

Troubleshooting flow.

Figure 6: Standard Docker container troubleshooting flow. docker logs is the first clue — don’t just stare at docker ps.

4. Root Cause: v3.7.0 CLI Subcommand Architecture

The Restaurant Analogy

Old system (v3.6.x and earlier): You walk into a restaurant and say “Kung Pao chicken, rice, cola.” The waiter knows you’re ordering food and places the order.

New system (v3.7.0): The restaurant installed a new POS. Now you must first say “Order”, then “Kung Pao chicken, rice, cola.” If you jump straight to “Kung Pao chicken,” the system is confused: “What operation is ‘Kung Pao chicken’? I only know ‘Order,’ ‘Pay,’ ‘Takeout.’”

Here, “Order” is the serve subcommand. “Kung Pao chicken” is --workspace and --accessAuthCode.

Technical Details

Before v3.7.0, the kernel entrypoint accepted top-level flags directly:

kernel --workspace=/siyuan/workspace/ --accessAuthCode=xxx

v3.7.0 introduced a full CLI subcommand architecture (implementing Issue #17674). The kernel now requires a subcommand:

kernel serve --workspace=/siyuan/workspace/ --accessAuthCode=xxx

The Docker image’s ENTRYPOINT is /opt/siyuan/entrypoint.sh. This script sets PUID/PGID and then calls kernel. But it does not automatically add serve — that responsibility falls on CMD or the compose command field.

In docker-compose.yml, the original config was:

command: ['--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']

These arguments were passed to entrypoint.sh, then to kernel. Kernel tried to parse --workspace as a subcommand name, failed, and exited with an error. Docker’s restart: always policy pulled it back up, and the cycle repeated.

CLI breaking change comparison.

Figure 7: v3.6.x vs v3.7.0 CLI argument structure comparison. The new serve subcommand is the only difference.

Note: The official README (line 216) explicitly documents this change: “Since v3.7.0, you must explicitly pass the serve subcommand (e.g., docker run b3log/siyuan serve --workspace=...).”

But when upgrading an existing deployment, many people (myself included) don’t re-read the entire README, which leads to this pitfall.

5. How to Fix It

5.1 The Fix

The fix is trivial: add 'serve' to the beginning of the command array in your compose file.

- command: ['--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']
+ command: ['serve', '--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']

Seven characters. From infinite restart loop to running normally.

Compose file diff.

Figure 8: The docker-compose.yml change — just add ‘serve’ at the beginning of the command array.

5.2 One-Click Fix Scripts

The following scripts work on three operating systems. They SSH into the Docker host, auto-detect the Siyuan container, back up the original config, patch the command field, and recreate the container.

Safety note: The scripts auto-backup the original docker-compose.yml (with timestamp). They never touch the data volume. Safe to roll back.

Windows 11 (PowerShell)

# siyuan-fix.ps1 — Siyuan v3.7.0 container fix script
# Usage: .\siyuan-fix.ps1 -ServerHost <docker-host> -ServerUser root -StackName siyuan-note-qiniu
param(
    [Parameter(Mandatory=$true)][string]$ServerHost,
    [string]$ServerUser = "root",
    [string]$StackName = "siyuan-note-qiniu"
)

$ErrorActionPreference = "Stop"

Write-Host "=== Siyuan v3.7.0 Docker Container Fix ===" -ForegroundColor Cyan

# 1. Check container status
Write-Host "[1/5] Checking container status..." -ForegroundColor Yellow
$status = ssh "${ServerUser}@${ServerHost}" "docker ps --filter name=$StackName --format '{{.Status}}'"
Write-Host "  Container status: $status"

# 2. Verify the error in logs
Write-Host "[2/5] Checking container logs..." -ForegroundColor Yellow
$logs = ssh "${ServerUser}@${ServerHost}" "docker logs --tail 20 `$(docker ps -a --filter name=siyuan --format '{{.Names}}' | head -1) 2>&1"
if ($logs -match "unknown flag.*accessAuthCode") {
    Write-Host "  ✓ Confirmed: v3.7.0 CLI breaking change" -ForegroundColor Green
} else {
    Write-Host "  ⚠ Unexpected error. Manual inspection needed." -ForegroundColor Red
    Write-Host $logs
    exit 1
}

# 3. Locate compose file
Write-Host "[3/5] Locating docker-compose.yml..." -ForegroundColor Yellow
$composePath = ssh "${ServerUser}@${ServerHost}" "docker inspect --format '{{index .Config.Labels \"com.docker.compose.project.config_files\"}}' `$(docker ps -a --filter name=siyuan --format '{{.ID}}' | head -1) 2>/dev/null"
if (-not $composePath) {
    $composePath = ssh "${ServerUser}@${ServerHost}" "grep -rl 'siyuan' /docker/portainer_data/compose/*/docker-compose.yml 2>/dev/null | head -1"
}
Write-Host "  Compose file: $composePath"

# 4. Backup and fix
Write-Host "[4/5] Backing up and fixing compose file..." -ForegroundColor Yellow
$fixCmd = @"
cp '$composePath' '$composePath.bak.'`$(date +%Y%m%d-%H%M%S) && \
sed -i "s|command: \\[\"--workspace|command: \\[\"serve\", \"--workspace|g" '$composePath' && \
echo 'FIXED' && grep -A 2 'command:' '$composePath'
"@
$result = ssh "${ServerUser}@${ServerHost}" $fixCmd
Write-Host $result
if ($result -match "FIXED") {
    Write-Host "  ✓ Compose file patched" -ForegroundColor Green
} else {
    Write-Host "  ⚠ Patch may have failed. Check manually." -ForegroundColor Red
    exit 1
}

# 5. Recreate container
Write-Host "[5/5] Recreating container..." -ForegroundColor Yellow
$composeDir = Split-Path $composePath -Parent
$rebuildResult = ssh "${ServerUser}@${ServerHost}" "cd '$composeDir' && docker compose -p $StackName up -d --force-recreate 2>&1"
Write-Host $rebuildResult

# Verify
Start-Sleep -Seconds 5
$finalStatus = ssh "${ServerUser}@${ServerHost}" "docker ps --filter name=siyuan --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"
Write-Host "`n=== Final Status ===" -ForegroundColor Cyan
Write-Host $finalStatus

$healthCheck = ssh "${ServerUser}@${ServerHost}" "curl -sf -o /dev/null -w '%{http_code}' http://127.0.0.1:6001/ 2>&1 || echo 'port may differ'"
Write-Host "HTTP status: $healthCheck (401 = healthy, access code active)"
Write-Host "`n✅ Fix complete!" -ForegroundColor Green

Ubuntu 26.04 / Debian

#!/usr/bin/env bash
# siyuan-fix.sh — Siyuan v3.7.0 container fix script
# Usage: bash siyuan-fix.sh <docker-host> [ssh-user] [stack-name]
set -euo pipefail

SERVER_HOST="${1:?Please specify Docker host address}"
SERVER_USER="${2:-root}"
STACK_NAME="${3:-siyuan-note-qiniu}"

RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
ssh_cmd() { ssh -o StrictHostKeyChecking=accept-new "${SERVER_USER}@${SERVER_HOST}" "$@"; }

echo -e "${CYAN}=== Siyuan v3.7.0 Docker Container Fix ===${NC}"

# 1. Check status
echo -e "${YELLOW}[1/5] Checking container status...${NC}"
ssh_cmd "docker ps -a --filter name=siyuan --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"

# 2. Confirm error in logs
echo -e "${YELLOW}[2/5] Confirming error in logs...${NC}"
CONTAINER_NAME=$(ssh_cmd "docker ps -a --filter name=siyuan --format '{{.Names}}' | head -1")
LOGS=$(ssh_cmd "docker logs --tail 20 ${CONTAINER_NAME} 2>&1" || true)
if echo "$LOGS" | grep -q "unknown flag.*accessAuthCode"; then
    echo -e "${GREEN}  ✓ Confirmed: v3.7.0 CLI breaking change${NC}"
else
    echo -e "${RED}  ⚠ Unexpected error. Manual inspection needed.${NC}"
    echo "$LOGS"
    exit 1
fi

# 3. Locate compose file
echo -e "${YELLOW}[3/5] Locating docker-compose.yml...${NC}"
COMPOSE_PATH=$(ssh_cmd "docker inspect --format '{{index .Config.Labels \"com.docker.compose.project.config_files\"}}' \$(docker ps -a --filter name=siyuan --format '{{.ID}}' | head -1) 2>/dev/null" || true)
if [[ -z "$COMPOSE_PATH" ]]; then
    COMPOSE_PATH=$(ssh_cmd "grep -rl 'siyuan' /docker/portainer_data/compose/*/docker-compose.yml 2>/dev/null | head -1" || true)
fi
if [[ -z "$COMPOSE_PATH" ]]; then
    echo -e "${RED}Cannot locate docker-compose.yml. Please specify manually.${NC}"
    exit 1
fi
echo "  Compose file: $COMPOSE_PATH"

# 4. Backup and fix
echo -e "${YELLOW}[4/5] Backing up and fixing...${NC}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
ssh_cmd "cp '$COMPOSE_PATH' '$COMPOSE_PATH.bak.$TIMESTAMP'" && echo "  ✓ Backed up to .bak.$TIMESTAMP"
ssh_cmd "sed -i \"s|command: \\\\[\\\"--workspace|command: \\\\[\\\"serve\\\", \\\"--workspace|g\" '$COMPOSE_PATH'"
echo -e "${GREEN}  ✓ Compose file patched${NC}"

# 5. Recreate container
echo -e "${YELLOW}[5/5] Recreating container...${NC}"
COMPOSE_DIR=$(dirname "$COMPOSE_PATH")
ssh_cmd "cd '$COMPOSE_DIR' && docker compose -p $STACK_NAME up -d --force-recreate 2>&1"

# Verify
sleep 5
echo -e "\n${CYAN}=== Final Status ===${NC}"
ssh_cmd "docker ps --filter name=siyuan --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"
HTTP_CODE=$(ssh_cmd "curl -sf -o /dev/null -w '%{http_code}' http://127.0.0.1:6001/ 2>&1" || echo "N/A")
echo -e "HTTP status: ${HTTP_CODE} (401 = healthy, access code active)"
echo -e "\n${GREEN}✅ Fix complete!${NC}"

macOS 26

#!/usr/bin/env bash
# siyuan-fix-mac.sh — Siyuan v3.7.0 container fix script (macOS)
# Usage: bash siyuan-fix-mac.sh <docker-host> [ssh-user] [stack-name]

set -euo pipefail

SERVER_HOST="${1:?Please specify Docker host address}"
SERVER_USER="${2:-root}"
STACK_NAME="${3:-siyuan-note-qiniu}"

RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
ssh_cmd() { ssh -o StrictHostKeyChecking=accept-new "${SERVER_USER}@${SERVER_HOST}" "$@"; }

echo -e "${CYAN}=== Siyuan v3.7.0 Docker Container Fix (macOS) ===${NC}"

if ! command -v ssh &>/dev/null; then echo -e "${RED}SSH client required${NC}"; exit 1; fi

# 1. Check
echo -e "${YELLOW}[1/5] Checking container status...${NC}"
ssh_cmd "docker ps -a --filter name=siyuan --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"

# 2. Confirm error
echo -e "${YELLOW}[2/5] Confirming error...${NC}"
CONTAINER_NAME=$(ssh_cmd "docker ps -a --filter name=siyuan --format '{{.Names}}' | head -1")
LOGS=$(ssh_cmd "docker logs --tail 20 ${CONTAINER_NAME} 2>&1" || true)
if echo "$LOGS" | grep -q "unknown flag.*accessAuthCode"; then
    echo -e "${GREEN}  ✓ Confirmed: v3.7.0 CLI breaking change${NC}"
else
    echo -e "${RED}  ⚠ Unexpected error${NC}"; echo "$LOGS"; exit 1
fi

# 3. Locate compose
echo -e "${YELLOW}[3/5] Locating compose file...${NC}"
COMPOSE_PATH=$(ssh_cmd "docker inspect --format '{{index .Config.Labels \"com.docker.compose.project.config_files\"}}' \$(docker ps -a --filter name=siyuan --format '{{.ID}}' | head -1) 2>/dev/null" || true)
[[ -z "$COMPOSE_PATH" ]] && COMPOSE_PATH=$(ssh_cmd "grep -rl 'siyuan' /docker/portainer_data/compose/*/docker-compose.yml 2>/dev/null | head -1" || true)
[[ -z "$COMPOSE_PATH" ]] && { echo -e "${RED}Cannot locate compose file${NC}"; exit 1; }
echo "  Compose: $COMPOSE_PATH"

# 4. Backup and fix (using python3 to avoid macOS sed quirks)
echo -e "${YELLOW}[4/5] Backing up and fixing...${NC}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
ssh_cmd "cp '$COMPOSE_PATH' '$COMPOSE_PATH.bak.$TIMESTAMP'" && echo "  ✓ Backed up"
ssh_cmd "python3 -c \"
import re
p = '$COMPOSE_PATH'
c = open(p).read()
c = c.replace('command: [\\\"--workspace', 'command: [\\\"serve\\\", \\\"--workspace')
open(p, 'w').write(c)
print('FIXED')
\""
echo -e "${GREEN}  ✓ Compose file patched${NC}"

# 5. Recreate
echo -e "${YELLOW}[5/5] Recreating container...${NC}"
COMPOSE_DIR=$(dirname "$COMPOSE_PATH")
ssh_cmd "cd '$COMPOSE_DIR' && docker compose -p $STACK_NAME up -d --force-recreate 2>&1"

sleep 5
echo -e "\n${CYAN}=== Final Status ===${NC}"
ssh_cmd "docker ps --filter name=siyuan --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"
HTTP_CODE=$(ssh_cmd "curl -sf -o /dev/null -w '%{http_code}' http://127.0.0.1:6001/ 2>&1" || echo "N/A")
echo -e "HTTP: ${HTTP_CODE} (401 = healthy)"
echo -e "\n${GREEN}✅ Fix complete!${NC}"

5.3 Manual Fix (No Scripts)

If you prefer to do it by hand:

# 1. SSH to the Docker host
ssh root@<your-docker-host>

# 2. Find the compose file (usually in Portainer's data directory)
grep -rl "siyuan" /docker/portainer_data/compose/*/docker-compose.yml

# 3. Back up the original
cp /path/to/docker-compose.yml /path/to/docker-compose.yml.bak.$(date +%Y%m%d-%H%M%S)

# 4. Edit the command line (add 'serve' at the beginning of the array)
# Before: command: ['--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']
# After:  command: ['serve', '--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']

# One-liner sed:
sed -i "s|command: \\[\"--workspace|command: \\[\"serve\", \"--workspace|g" /path/to/docker-compose.yml

# 5. Verify the change
grep "command:" /path/to/docker-compose.yml

# 6. Recreate the container
cd $(dirname /path/to/docker-compose.yml)
docker compose -p siyuan-note-qiniu up -d --force-recreate

# 7. Verify
docker ps --filter name=siyuan
curl -o /dev/null -w "%{http_code}\n" http://127.0.0.1:6001/
# 401 = access code is active, service is healthy!

docker compose up success output.

Figure 9: Real terminal screenshot — after recreating the container, Status shows Up, and curl returns 401 (access code active).

6. Manual vs. AI Agent Fix

6.1 Manual Fix

The estimated time for a human to fix this is 15–30 minutes:

In practice, most of the time is spent on “what does this error mean?” The unknown flag message is not immediately intuitive for someone unfamiliar with CLI subcommand design.

6.2 AI Agent Fix

If you let an AI Agent handle it, the prompt might look like:

My Siyuan Docker container keeps restarting. Please diagnose and fix it.

Constraints:
1. Do not perform major OS upgrades
2. Do not download unknown installers
3. Do not output real private IPs, full computer names, private domains, or keys
4. Back up config files before modifying them
5. Verify container status and HTTP reachability after the fix

The Agent’s execution flow:

  1. docker ps -a → finds Restarting container
  2. docker logs → sees unknown flag: --accessAuthCode
  3. docker inspect → confirms compose config and image version
  4. Reads official README → discovers v3.7.0 serve subcommand requirement
  5. Backs up → patches compose → recreates container → verifies

Real-world result: From start to fix, the Agent took about 10 minutes. A human, accounting for searching, reading docs, and understanding CLI architecture changes, would typically need 20–30 minutes.

AI Agent fix workflow.

Figure 10: Complete AI Agent workflow for remote Docker container troubleshooting.

6.3 Guardrails for the Agent

When letting an Agent operate on production, these guardrails are essential:

Guardrail Description
Backup first Always back up config files before modifying
No OS upgrades Agent operates at container level only
No privacy leaks Never output real IPs, domains, or keys in logs or configs
Diagnose before acting Confirm root cause via logs/inspect before making changes
Verify the fix Check container status and HTTP reachability after the fix
Rollback-ready Keep backup files and rollback commands available

7. Post-Fix Verification

After the fix, Siyuan should be running normally. Visiting http://<your-host>:<port>/ (port 6001 in this example) will show the Siyuan lock screen:

Siyuan lock screen login page.

Figure 11: Real screenshot — Siyuan v3.7.0 lock screen (full size). Enter your accessAuthCode to access your notes.

Enter the password set in --accessAuthCode to access your notes. An HTTP 401 response (when testing with curl) is actually good — it means the access control is working.

$ curl -o /dev/null -w "%{http_code}\n" http://<host>:<port>/
401

HTTP 401 is not an error here — it means Siyuan’s access control is active. Only users with the correct password can enter.

8. Q&A

Q1: Why did Siyuan introduce this breaking change in v3.7.0?

Because v3.7.0 added a full CLI. The kernel is no longer just an HTTP server — it’s a multi-function command-line tool. Beyond serve (start the HTTP server), there are export, import, sync, and other subcommands. Breaking functionality into subcommands is standard CLI design, just like git commit, git push, etc.

Q2: I’m still on v3.6.x. Should I upgrade?

Yes. v3.7.0 brings a new UI, kernel plugin system, and AI knowledge base — it’s well worth upgrading. Just make sure your docker-compose.yml command field includes the serve subcommand before you do.

Q3: What if I have multiple Siyuan containers to fix?

The one-click scripts above accept a stack name parameter. Loop through each compose directory and run the fix.

Q4: How do I fix this in Portainer?

In Portainer, go to the stack, click Editor, add 'serve', at the beginning of the command array, and click “Update the stack.” Portainer will recreate the container automatically.

Q5: Will I lose my data?

No. Data lives in /siyuan/workspace (or your mounted directory). The fix only changes the command field in the compose file — it never touches the data volume. Plus, the script backs up the compose file before making changes.

Q6: Is there an easier fix than editing the compose file?

If you use docker run instead of compose, just add serve to the command:

docker run -d \
  -v /siyuan/workspace:/siyuan/workspace \
  -p 6806:6806 \
  b3log/siyuan \
  serve \
  --workspace=/siyuan/workspace/ \
  --accessAuthCode=xxx

Q7: Can this happen with other Docker images?

Yes. Any software that transitions from a flat CLI to a subcommand-based CLI can cause similar issues. Before upgrading, check the CHANGELOG for keywords like “CLI,” “command,” or “breaking change.”

9. Conclusion

Three takeaways from this debugging session:

  1. When a container fails, read docker logs first. docker ps tells you the container is restarting. docker logs tells you why.
  2. Read the CHANGELOG before upgrading. v3.7.0’s README explicitly documents the serve subcommand requirement, but it’s easy to miss when upgrading an existing deployment.
  3. Back up before you fix. cp file.yml file.yml.bak.$(date) takes two seconds and can save you when things go wrong.

From a technical perspective, the root cause is a CLI interface breaking change. The Siyuan team made the right architectural decision (introducing a subcommand architecture) and documented it, but breaking changes always hit existing deployments the hardest. Fortunately, the fix is trivial — one word, seven characters: serve.

From an operations perspective, this case perfectly demonstrates the value of the “logs first” troubleshooting methodology: don’t guess, don’t try random things, don’t rush to roll back — read the logs. The logs will tell you everything.

If you ran into the same issue, I hope this article saves you 15 minutes of head-scratching.

References