Self-hosted Markdown Notes with Web UI and Claude Code CLI via SSH.
| Service | Purpose | Access |
|---|---|---|
| NoteDiscovery | Web UI for Markdown Notes | http://server:8800 |
| Claude Code | AI Assistant via SSH | ssh -p 2222 root@server |
Both containers share the same notes directory.
project/
├── docker-compose.yml
├── .env # Create from .env.example
├── .env.example
├── claude-ssh/
│ ├── Dockerfile
│ └── entrypoint.sh
├── claude-data/ # Created automatically (Claude Config)
└── notes/ # Your Markdown Notes
cp .env.example .envEdit .env:
GITHUB_USERS=your-github-username
ANTHROPIC_API_KEY=sk-ant-xxxxxMultiple GitHub users: GITHUB_USERS=user1,user2,user3 (no spaces)
mkdir -p notesdocker compose up -d --buildFirst build takes ~5 minutes (npm install).
Open browser: http://server:8800
ssh -p 2222 root@server
cd /workspace/notes
claudeUseful Claude Code commands:
/status- Auth method, model, account/cost- API usage costs/doctor- Check installation/init- Create CLAUDE.md for project
The container automatically fetches SSH keys from GitHub on startup:
- Reads
GITHUB_USERSenvironment variable ssh-import-id-ghfetches keys fromhttps://github.com/<user>.keys- Keys are written to
/root/.ssh/authorized_keys
Instead of browser OAuth, the API key is configured directly:
- Key is exported in
/root/.bashrc ~/.claude.jsonmarks the key as trusted (last 20 characters)- Onboarding is skipped
| Path | Bind Mount | Persistent? |
|---|---|---|
/workspace/notes |
./notes |
✅ |
/root/.claude |
./claude-data |
✅ |
/root/.claude.json |
- | ❌ (recreated on start) |
# Check if key exists on GitHub
curl https://github.com/YOUR_USERNAME.keys
# Check container logs
docker compose logs claude-code# Check inside container
docker exec -it claude-code bash
echo $ANTHROPIC_API_KEY
cat /root/.claude.jsondocker compose logs -f
docker compose logs -f claude-codedocker compose down && docker compose up -d --buildAdjust both volumes in docker-compose.yml:
volumes:
- /your/path:/app/data # NoteDiscovery
- /your/path:/workspace/notes # Claude Codenotediscovery:
environment:
- AUTHENTICATION_ENABLED=true
- AUTHENTICATION_PASSWORD=your-passwordIn Claude Code: /model or on startup: claude --model opus
Since /root/.ssh is not persistent, use GitHub Personal Access Token:
# Inside the claude-code container
ssh -p 2222 root@server
# Configure git to store credentials in persistent directory
git config --global user.name "your-name"
git config --global user.email "your@email.com"
git config --global credential.helper 'store --file=/root/.claude/.git-credentials'git push
# Username: your-github-username
# Password: <paste your personal access token>The token is stored in /root/.claude/.git-credentials (persistent bind mount).
Create Personal Access Token:
- https://github.com/settings/tokens/new
- Note:
claude-workspace - Expiration: 90 days or longer
- Scopes: Check
repo - Generate and copy token
Note: Credentials persist across container restarts since /root/.claude is mounted. However, full persistence has not been extensively tested.