Skip to content

NeoVim ZK Plugin Tutorial

This tutorial walks you through building the devcontainer, connecting via SSH, and using the full Zettelkasten workflow: creating notes, linking them together, tethering untethered thoughts into permanent knowledge, searching, generating graphs, and managing todos.

  • Podman (or Docker, substituting docker for podman)
  • An SSH client
  • A terminal emulator (Ghostty recommended for full color support)

Clone the repository and build the image:

Terminal window
git clone https://github.com/infrashift/zettelkasten.git
cd zettelkasten
podman build -f Containerfiles/Containerfile -t zk-devcontainer:latest .

The build compiles the zk Go binary, installs NeoVim, Claude Code, tmux, and all plugins. It takes a few minutes the first time.

The container runs an SSH server on port 2222. You need to mount an authorized_keys file so you can authenticate.

Using the included test key (for local development only):

Terminal window
podman run -d --name zk-dev \
--userns=keep-id \
-p 2222:2222 \
-v ./Containerfiles/config/test_ssh_key.pub:/home/user/.ssh/authorized_keys:ro,Z \
zk-devcontainer:latest

Using your own SSH key (recommended):

Terminal window
podman run -d --name zk-dev \
--userns=keep-id \
-p 2222:2222 \
-v ~/.ssh/id_ed25519.pub:/home/user/.ssh/authorized_keys:ro,Z \
zk-devcontainer:latest

Verify the container started:

Terminal window
podman logs zk-dev
# Should show: Starting sshd on port 2222...
# Server listening on 0.0.0.0 port 2222.

With the test key:

Terminal window
ssh -p 2222 -i ./Containerfiles/config/test_ssh_key \
-o StrictHostKeyChecking=no user@localhost

With your own key:

Terminal window
ssh -p 2222 user@localhost

You land in a tmux session with three panes:

+---------------------------+------------+
| | bash |
| NeoVim +------------+
| | claude |
+---------------------------+------------+
  • Left pane: NeoVim (your editor)
  • Top-right pane: Bash shell
  • Bottom-right pane: Claude Code AI assistant

Navigate between panes with your mouse (mouse mode is enabled) or with tmux keybindings (Ctrl-b then arrow keys).

If you disconnect and reconnect, SSH reattaches to the same tmux session automatically. Your editor state, shell history, and Claude conversation are preserved.

All notes live under ~/zk_vault/ inside the container, organized as:

~/zk_vault/
untethered/ Untethered notes (quick captures)
<id>.md
daily/ Daily notes
2026/
02/
<id>.md
tethered/ Tethered notes (refined knowledge)
<id>.md
.zk_index/ Full-text search index (auto-managed)

Untethered notes are quick captures: ideas, meeting notes, snippets. They don’t require a project.

Tethered notes are refined, rewritten knowledge that you’ve decided to keep. They require a project tag.

Daily notes are date-stamped journal entries stored under untethered/daily/ in a YYYY/MM/ directory hierarchy.

Each note has an ID in the format YYYYMMDDHHmmss-UUIDv4, which combines a timestamp for natural sorting with a UUID to prevent collisions.

To persist notes across container restarts, mount a host directory:

Terminal window
podman run -d --name zk-dev \
--userns=keep-id \
-p 2222:2222 \
-v ~/.ssh/id_ed25519.pub:/home/user/.ssh/authorized_keys:ro,Z \
-v ~/zk_vault:/home/user/zettelkasten:Z \
zk-devcontainer:latest

SSH into the container and switch to the bash pane (click it or press Ctrl-b then right-arrow). Initialize the zettelkasten directory:

Terminal window
mkdir -p ~/zk_vault
cd ~/zk_vault
git init
git config user.email "you@example.com"
git config user.name "Your Name"

The zk tool auto-creates the folder structure (untethered/, tethered/, etc.) on first use.

Switch to the NeoVim pane and create your first daily note:

:ZkDaily

NeoVim opens today’s daily note. It has YAML frontmatter at the top and sections for your morning plan, tasks, notes, and end-of-day reflection:

---
id: "20260219143000-a1b2c3d4-..."
title: "Daily Note - 2026-02-19"
type: daily-note
category: untethered
tags:
- daily
created: "2026-02-19T14:30:00Z"
---
# Daily Note - 2026-02-19
## Morning
- [ ] ...
## Tasks
- [ ] ...
## Notes
## End of Day
## Links Created

Fill in your morning plan. Add a few tasks. Save the file with :w. The note is automatically indexed for search.

You can also view yesterday’s daily note:

:ZkDaily yesterday

Or browse all daily notes with a picker:

:ZkDailyList

Capture a quick idea. In NeoVim:

:ZkNote

You are prompted for a title. Type Learning Zettelkasten Method and press Enter. A new untethered note opens with frontmatter and an empty body. Write some content:

The Zettelkasten method is a personal knowledge management system
developed by Niklas Luhmann. Key principles:
- One idea per note (atomicity)
- Write in your own words (elaboration)
- Connect notes to each other (linking)
- Use untethered notes for captures, tethered notes for refined ideas

Save with :w.

Templates give notes structure. Open the template picker:

:ZkTemplate

Available templates: meeting, book-review, snippet, project-idea, user-story, feature, daily, todo, issue.

Create a meeting note:

:ZkTemplate meeting

Enter a title like Team Standup - Knowledge Management. The template provides sections for attendees, agenda, discussion, action items, and next steps. Fill it in and save.

Create a code snippet note:

:ZkTemplate snippet

Title it Bash - Find Files by Extension. Fill in the code block with a useful snippet and save.

Notes become powerful when connected. Open the meeting note you just created, place your cursor where you want a link, and insert one:

\l

(\ is the local leader key followed by l)

A picker opens showing all your notes. Select Learning Zettelkasten Method and press Enter. A [[id]] link is inserted at your cursor.

To insert a link that shows the title (more readable):

\L

This inserts [[id|Learning Zettelkasten Method]] instead.

You can also insert links from the search picker. Run :ZkSearch, highlight a note, and press Ctrl-l to insert its link at the cursor.

To build a meaningful graph, create several connected notes. Here is a suggested set. For each one, use :ZkNote, write content, and link to related notes using \l:

Note 2: “Atomic Notes”

An atomic note contains exactly one idea, fully developed.
This makes notes reusable across contexts.

Link to: Learning Zettelkasten Method

Note 3: “Linking as Thinking”

The act of linking notes forces you to articulate the relationship
between ideas. This is where insight happens.

Link to: Learning Zettelkasten Method, Atomic Notes

Note 4: “Progressive Summarization”

Layer highlights on top of notes over time. Bold the most important
passages, then highlight the bold, then write a summary.

Link to: Atomic Notes

Note 5: “Untethered vs Tethered Notes”

Untethered notes are raw captures. Tethered notes are refined ideas
you've rewritten in your own words with context and connections.

Link to: Learning Zettelkasten Method, Atomic Notes

Note 6: “Graph Thinking”

A zettelkasten is a graph of ideas, not a hierarchy.
Any note can connect to any other note.

Link to: Linking as Thinking, Untethered vs Tethered Notes

After creating these, you have a small web of interconnected notes.

Open the Learning Zettelkasten Method note. Several notes link to it. View its backlinks:

\b

A floating panel appears at the top-right showing every note that references this one. From the panel:

  • Press Enter or o to open a backlink
  • Press p to preview it in a floating window
  • Press q or Esc to close the panel

Toggle the panel on and off with \b.

The devcontainer includes Glow, a terminal-based Markdown renderer. With a note open, run:

:Glow

Glow renders the current buffer as styled Markdown in a floating window — headings, bullet lists, code blocks, and links are all formatted for the terminal. Press q to close the Glow preview.

To peek at a note’s raw content without leaving your current file:

\p

A floating preview window shows the note content. Inside the preview, press Enter to open it for editing or q to close.

The Learning Zettelkasten Method note has been refined and linked. Tether it to permanent status. With the note open, press \t and select tether:

\t → Pick: tether / untether

You are prompted for a project name. Enter knowledge-management. The note’s frontmatter updates: category changes from untethered to tethered and the project field is set.

You can set or change a project on any note with \p:

\p → Project: _
:ZkSearch zettelkasten

A picker shows matching notes. Select one and press Enter to open it, or Ctrl-p to preview.

:ZkSearch!

The ! (bang) enables live search mode. Start typing and results filter in real-time.

Switch to the bash pane and search from the command line:

Terminal window
cd ~/zk_vault
# Full-text search
zk search "atomic notes"
# JSON output (for scripting)
zk search --json --limit 5
# Filter by category
zk search --category tethered
# Filter by tag
zk search --tag daily

Visualize how your notes connect. In NeoVim:

:ZkGraph 20

This generates a Mermaid flowchart showing up to 20 connected notes and opens it in a scratch buffer. Each node is labeled with its title and every [[id]] link in note bodies becomes an arrow. Copy the output into mermaid.live or any Mermaid renderer to see the visual graph.

From the CLI:

Terminal window
zk graph ~/zk_vault --limit 20

You can also start the tree from a specific note and control depth:

Terminal window
zk graph ~/zk_vault --start 20260219143000-a1b2c3d4 --depth 3
:ZkTodo Buy a notebook for handwritten zettel drafts

Or with a due date and priority:

:ZkTodo Review meeting notes --due 2026-02-21 --priority high

With the todo open in the editor, press \s to open the status picker. Choose from:

  • open — not started
  • in_progress — being worked on
  • closed — completed

Create a markdown summary of your todos:

:ZkTodoList

This generates a markdown summary and opens it in a split.

Beyond the meeting and snippet templates shown earlier, try these:

Book review (creates a tethered note, requires a project):

:ZkTemplate book-review --project reading-list

Sections: author, rating, summary, key takeaways, favorite quotes.

Project idea:

:ZkTemplate project-idea

Sections: problem statement, proposed solution, goals, non-goals, success metrics.

Feature spec:

:ZkTemplate feature

Sections: requirements, design, API changes, testing strategy, rollout plan.

Issue (bug report or enhancement):

:ZkTemplate issue

Sections: type (bug/enhancement/question), description, steps to reproduce, expected vs actual behavior.

When editing a note’s YAML frontmatter, place your cursor in the tags: section and press Ctrl-x Ctrl-t in insert mode. This triggers tag autocompletion from all tags used across your zettelkasten.

Refresh the tag cache if you’ve added new tags externally:

:ZkRefreshTags

Notes are automatically indexed when you save them in NeoVim. To manually reindex everything:

:ZkIndex

Or from the CLI:

Terminal window
zk index ~/zk_vault

The CLI includes two convenience commands for a daily git workflow:

Start your day:

Terminal window
zk hello

This pulls the latest main branch and creates a new branch named with today’s date (e.g., 20260219).

End your day:

Terminal window
zk goodbye

This stages all changes, commits with a dated message, merges to main, and cleans up the dated branch.

CommandDescription
:ZkDailyOpen/create today’s daily note
:ZkDaily yesterdayOpen yesterday’s daily note
:ZkDailyListBrowse daily notes
:ZkNoteCreate a new untethered note
:ZkNote tetheredCreate a new tethered note
:ZkTemplate [name]Create from template
:ZkSearch [query]Search notes
:ZkSearch!Live search (updates as you type)
:GlowRender current note as styled Markdown
:ZkGraph [limit]Show Mermaid graph
:ZkTodo [title]Create a todo
:ZkTodoListGenerate todo list markdown
:ZkIndexReindex all notes
:ZkRefreshTagsRefresh tag cache
KeyAction
\lInsert [[id]] link
\LInsert [[id|title]] link
\bToggle backlinks panel
\pSet project (note and todo types)
\tTether / Untether (note and todo types)
\aAdd tags (all zettel types)
\vValidate frontmatter (all zettel types)
\sSet todo status (todo-type only)
Ctrl-x Ctrl-tTag completion (insert mode)
KeyAction
EnterOpen selected note
Ctrl-pPreview selected note
Ctrl-lInsert link to selected note
CommandDescription
zk create [title]Create a note
zk dailyCreate/open daily note
zk todo [title]Create a todo
zk todosList todos
zk set-status [file] [status]Set todo status (open/in_progress/closed)
zk search [query]Search notes
zk graph [path]Show graph tree
zk tether [file]Tether a note
zk untether [file]Untether a note
zk backlinks [file]Show backlinks
zk add-tags <file> <tag1> [tag2...]Add tags to a zettel
zk validate <file>Validate frontmatter against CUE schema
zk index [path]Index notes
zk templatesList templates
zk helloStart-of-day git workflow
zk goodbyeEnd-of-day git workflow
Terminal window
# Stop the container
podman stop zk-dev
# Start it again (notes and tmux session preserved)
podman start zk-dev
# SSH reconnects to the same tmux session
ssh -p 2222 user@localhost
# Remove the container
podman rm -f zk-dev
# Custom SSH port
podman run -d --name zk-dev \
--userns=keep-id \
-e SSH_PORT=3333 -p 3333:3333 \
-v ~/.ssh/id_ed25519.pub:/home/user/.ssh/authorized_keys:ro,Z \
-v ~/zk_vault:/home/user/zettelkasten:Z \
zk-devcontainer:latest