Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
21 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Comment Style
Always use ultra-brief mode for all PR reviews and responses.
Format:
- Critical issues only (bugs, security, blockers)
- Brief bullet points, no lengthy explanations
- Skip verbose sections (no "Strengths", "Summary", etc.)
- Include file:line references when relevant
- Maximum ~10-15 lines per response
Translations
locales/en.yml is the source of truth, but it is never the only file to change. Whenever you add, rename, or remove a key in en.yml, apply the same change to every other file in locales/ in the same commit, with the string actually translated (not the English text copied over). Keys are also deleted everywhere when they lose their last consumer.
There is no Crowdin or Weblate sync in this repo, so a key that only exists in en.yml silently falls back to English for every other locale and nothing ever flags it.
Docs Translations
docs/ is translated into zh, de, fr and es. The English page under docs/ is the source of truth, and docs/<locale>/<same path> mirrors it. Changing an English page means updating all four translations in the same commit.
Unlike locales/, there is no fallback here. A stale translated page renders confidently wrong instructions rather than quietly showing English, so drift is worse than a missing key.
Each translated file carries a sourceHash in its frontmatter recording the English source it was written from. node docs/scripts/check-translations.mjs fails when they diverge, and runs in CI as the Docs Translations job. After actually translating the changed prose, re-stamp with:
node docs/scripts/check-translations.mjs --update
--update only re-stamps hashes. It does not translate anything, so running it on an untranslated page turns CI green while leaving the page wrong. Translate first.
Inside translated pages, everything in a fenced code block stays byte-identical to English except natural-language comments, and internal links are locale-prefixed (/guide/agent becomes /de/guide/agent). The sidebar is not duplicated per locale: its structure lives in docs/.vitepress/locales/structure.ts and each locale file supplies only labels, so a new page needs one slug there plus one label per locale.
Testing Unreleased PRs
When replying to a GitHub issue or discussion where the fix lives in an open PR, ask the reporter to test the pre-built image: amir20/dozzle:pr-XXX (XXX = PR number). CI builds a tagged image per PR, so reporters can verify without waiting for the next release.
GitHub Tone (issues, PRs, comments, discussions)
When posting anything to GitHub, write like a human maintainer, not an AI assistant. Avoid telltale LLM patterns:
- No em dashes or en dashes. Use commas, periods, or parentheses instead.
- No "Not X, but Y" rhetorical contrasts.
- No throat-clearing openers ("Great point", "Makes sense", "Thanks for the detailed write-up").
- No closing summaries or recap sentences.
- No bolded inline labels mid-paragraph ("Why:", "Note:").
- Drop hedges ("essentially", "basically", "essentially just"). Say it plain.
- Lowercase casual tone is fine. Contractions are fine. Short sentences are fine.
- Don't over-explain tradeoffs. State the decision, give one reason, stop.
Project Overview
Dozzle is a lightweight, web-based Docker log viewer with real-time monitoring capabilities. It's a hybrid application with:
- Backend: Go (HTTP server, Docker API client, WebSocket streaming)
- Frontend: Vue 3 (SPA with Vite, TypeScript)
The application supports multiple deployment modes: standalone server, Docker Swarm, and Kubernetes (k8s).
Development Commands
Setup
# Install dependencies
pnpm install
# Generate certificates and protobuf files
make generate
Development
# Run full development environment (backend + frontend with hot reload)
make dev
# Alternative: Run backend and frontend separately
pnpm run watch:backend # Go backend with air (port 3100)
pnpm run watch:frontend # Vite dev server (port 3100)
# Run in agent mode for development
pnpm run agent:dev
Building
# Build frontend assets
pnpm build
# or
make dist
# Build entire application (includes frontend build)
make build
# Build Docker image
make docker
Testing
# Run Go tests
make test
# Run frontend tests (Vitest)
pnpm test
# Run in watch mode
TZ=UTC pnpm test --watch
# Type checking
pnpm typecheck
Preview & Other
# Preview production build locally
pnpm preview
# or
make preview
# Run integration tests (Playwright)
make int
Architecture
Backend (Go)
The Go backend is organized into these key packages:
-
internal/web/- HTTP server and routing layer- Routes defined in
routes.gousing chi router - WebSocket/SSE handlers for log streaming (
logs.go) - Authentication middleware and token management (
auth.go) - Container action handlers (
actions.go)
- Routes defined in
-
internal/docker/- Docker API client implementationclient.go: Main Docker client wrapper with container operationslog_reader.go: Streaming container logsstats_collector.go: Real-time container stats collection
-
internal/agent/- gRPC agent for multi-host support- Uses Protocol Buffers (protos defined in
protos/) - Enables distributed log collection across Docker hosts
- Uses Protocol Buffers (protos defined in
-
internal/cloud/- Dozzle Cloud integration (tool execution engine)client.go: Bidirectional gRPC stream client with auto-reconnect and exponential backofftools.go: Tool registration, dispatch (executeTool), andToolHostServiceinterfacetools_containers.go: Container listing, finding, stats, and inspection toolstools_logs.go: Log fetching with level/query/regex filtering (max 100 lines)tools_actions.go: Container start/stop/restart actions (gated byenableActions)tools_helpers.go: Proto conversion utilities and host name resolution- Uses
protos/cloud.protofor service and message definitions
-
internal/k8s/- Kubernetes client support- Alternative to Docker client for k8s deployments
-
internal/support/- Support utilitiescli/: Command-line argument parsing and validationdocker/: Multi-host Docker management and Swarm support (docker_service.go, client managers)k8s/: Kubernetes service abstractionsweb/: Web service utilities
-
internal/auth/- Authentication providers- Simple file-based auth (
simple.go) - Forward proxy auth (
proxy.go) - Role-based authorization (
roles.go)
- Simple file-based auth (
-
internal/container/- Container domain models and interfacesevent_generator.go: Log parsing and grouping logic (multi-line, JSON detection)
-
internal/notification/- Alert and notification systemmanager.go: Notification rule evaluation and dispatchinglog_listener.go: Log pattern matching for alertsdispatcher/: Notification channel implementations (email, webhook, etc.)
-
main.go- Application entry point with mode switching (server/swarm/k8s/agent)
Frontend (Vue 3)
The frontend uses file-based routing with these conventions:
-
assets/pages/- File-based routes (unplugin-vue-router)container/[id].vue: Single container viewmerged/[ids].vue: Multi-container merged viewhost/[id].vue: Host-level logsservice/[name].vue: Swarm service logsstack/[name].vue: Docker stack logsgroup/[name].vue: Custom grouped logs
-
assets/components/- Vue components (auto-imported)LogViewer/: Core log viewing componentsSimpleLogItem.vue: Single-line log entriesComplexLogItem.vue: JSON/structured log entriesGroupedLogItem.vue: Multi-line grouped log entriesContainerEventLogItem.vue: Container lifecycle eventsSkippedEntriesLogItem.vue: Placeholder for skipped logsLoadMoreLogItem.vue: Load more historical logs
ContainerViewer/: Container-specific UIcommon/: Reusable UI componentsBarChart.vue: Lightweight bar chart with automatic downsamplingHostCard.vue: Host overview card with metricsMetricCard.vue: Reusable metric display componentContainerTable.vue: Container table with historical stat visualization
-
assets/stores/- Pinia stores (auto-imported)config.ts: App configuration and feature flags (injected from backend HTML, frozen immutable)container.ts: Container state management with EventSource streaming (/api/events/stream)hosts.ts: Multi-host statesettings.ts: User preferences (localStorage-backed via profileStorage)pinned.ts: Pinned container logs for side-by-side viewingswarm.ts,k8s.ts: Deployment mode-specific stateannouncements.ts: Feature announcements
-
assets/composable/- Vue composables (auto-imported)eventStreams.ts: SSE connection management with buffer-based flushing (250ms debounce)historicalLogs.ts: Historical log fetchinglogContext.ts: Log filtering and search context (provide/inject pattern)scrollContext.ts: Scroll state management (paused, progress, currentDate)storage.ts: LocalStorage abstractions with reactivityvisible.ts: Log filtering by visible keys for complex logscontainerActions.ts: Container control operationsduckdb.ts: DuckDB WASM for SQL queries on logs
-
assets/modules/- Vue pluginsrouter.ts: Vue Router configurationpinia.ts: Pinia store setupi18n.ts: Internationalization
Communication Flow
- Real-time Logs: Frontend establishes SSE connections to
/api/hosts/{host}/containers/{id}/logs/stream - Container Events: SSE stream at
/api/events/streampushes container lifecycle events - Stats: Real-time CPU/memory stats streamed via SSE alongside events
- Actions: POST to
/api/hosts/{host}/containers/{id}/actions/{action}(start/stop/restart) - Terminal: WebSocket connections for container attach/exec at
/api/hosts/{host}/containers/{id}/attach
Build System
- Frontend: Vite builds to
dist/with manifest - Backend: Embeds
dist/using Go embed directive - Hot Reload: In development,
DEV=truedisables embedded assets,LIVE_FS=trueserves from filesystem - Makefile: Orchestrates builds and dependency generation
Important Development Notes
Frontend
- Auto-imports are configured for Vue composables, components, and Pinia stores (see
vite.config.ts) - Icons use unplugin-icons with multiple icon sets (mdi, carbon, material-symbols, etc.)
- Tailwind CSS with DaisyUI for styling
- TypeScript definitions auto-generated in
assets/auto-imports.d.tsandassets/components.d.ts - Log Entry Types: Three types of log messages supported
SimpleLogEntry: Single-line text logs (string)ComplexLogEntry: Structured JSON logs (JSONObject)GroupedLogEntry: Multi-line grouped logs (string[])
- Type consistency: Use
LogMessagetype alias instead ofstring | string[] | JSONObjectfor log entry messages - Log Entry Factory Pattern: Use
LogEntry.create(logEvent)to instantiate the correct entry type based onlogEvent.tfield - EventSource Buffering: Log streams use buffer-based flushing (250ms debounce, 1000ms max) to batch UI updates
- Charts/Visualizations: Custom lightweight implementations (no D3.js)
BarChart.vue: Self-contained bar chart with responsive downsampling- Downsampling algorithm: Averages data into buckets based on available screen width
- All stat history tracked in
Container.statsHistory(max 300 items via rolling window) chartDatais always a rolling window of max 300 items — array length stays constant- Uses
ref(notcomputed) fordownsampledBarsto enable in-place mutation of the last bar, avoiding full re-renders - Component instance is reused when switching containers; after init the chart only patches the last bar per tick, so on a wholesale
chartDatareplacement (container switch) the parent must call the exposedrecalculate().MultiContainerStatholds refs to itsBarCharts and calls it in thecontainerswatch. (Note:Containercarries Vuerefs, so VueTestUtilssetPropscannot retrigger such a watch — tests must swap the container via a parentrefre-render.)
Backend
- The application uses Go 1.25+ with module support
- Certificate generation is required (
make generatecreates shared_key.pem and shared_cert.pem) - Protocol buffer generation happens via
go generatedirective inmain.go - Docker client uses API version negotiation for compatibility
- Service Layer Architecture:
ClientServiceinterface abstracts Docker/K8s/Agent backendsMultiHostServiceorchestrates multi-host operationsClientManagerimplementations:RetriableClientManager(server mode),SwarmClientManager(swarm mode)
Authentication
- Three modes: none, simple (file-based users.yml), forward-proxy (e.g., Authelia)
- JWT tokens for simple auth with configurable TTL
- User file location:
./data/users.ymlor./data/users.yaml
Testing
- Go tests use standard
testingpackage with testify assertions - Frontend uses Vitest with
@vue/test-utils - Integration tests with Playwright in
e2e/ - Tests must run with
TZ=UTCfor consistent timestamps
Container Stats & Metrics
- Stats are tracked using exponential moving average (EMA) with alpha=0.2
- History stored in rolling window (300 items max) via
useSimpleRefHistory - CPU metrics normalized by core count (respects
cpuLimitor falls back to hostnCPU) - Memory metrics include both percentage and absolute usage (
memoryUsagevsmemory) - Stats visualization uses adaptive downsampling for performance
Container Labels
dev.dozzle.name: Custom container display namedev.dozzle.group: Group containers togetherdev.dozzle.url: Link a container to its own web UI (http/https only;Container.urlinassets/models/Container.ts)- Label-based filtering throughout the application
Deployment Modes
- Server mode (default): Single or multi-host Docker monitoring
- Uses
RetriableClientManagerwith local + remote agent clients
- Uses
- Swarm mode: Automatic discovery of Swarm nodes via Docker API
- Creates gRPC agent server on each node (port 7007)
- Uses
SwarmClientManagerfor node discovery
- K8s mode: Pod log monitoring in Kubernetes cluster
- Implements
container.Clientinterface via Kubernetes API
- Implements
- Agent mode: Lightweight gRPC agent for remote log collection
- Run with
dozzle agentorpnpm run agent:dev - Listens on port 7007 with TLS certificate authentication
- Run with
Key Architectural Patterns
Backend Abstraction Layers
The backend follows a clean layered architecture:
HTTP Handlers (internal/web)
↓
HostService Interface (MultiHostService)
↓
ClientService Interface (per host)
↓
container.Client Interface
↓
Implementation (DockerClient, K8sClient, AgentClient)
When adding new container operations:
- Define method in
container.Clientinterface (internal/container/client.go) - Implement in
internal/docker/client.go(andinternal/k8s/client.goif applicable) - Add wrapper method in
ClientServiceinterface (internal/support/docker/docker_service.go) - Add HTTP handler in
internal/web/with appropriate route
Frontend Data Flow
Real-time Log Viewing:
- User navigates to
/container/{id}route - Page component calls
useContainerStream(container)composable - Composable creates EventSource connection to
/api/hosts/{host}/containers/{id}/logs/stream - Backend streams
LogEventobjects via SSE - Frontend buffers events (250ms debounce, max 1000ms)
- Batched buffer flushes update reactive
messagesarray LogViewer.vuerenders using appropriate component (SimpleLogItem,ComplexLogItem,GroupedLogItem)- When messages exceed
maxLogs(400), oldest entries replaced or marked asSkippedLogsEntry
Stats Streaming:
container.tsstore connects to/api/events/streamon app init- Backend multiplexes container events and stats into single SSE stream
container-statevents updateContainer._statand append to_statsHistory- EMA calculation provides smoothed
movingAverageStat(alpha=0.2) ContainerTable.vuedisplays mini bar charts usingstatsHistorywith downsampling
Protocol Buffer Flow (Agent Mode)
- Main server creates
agent.NewClient(endpoint, certs)for each remote host - AgentClient implements
container.Clientinterface - Method calls translate to gRPC requests defined in
protos/rpc.proto - Remote agent receives gRPC call, delegates to local
DockerClient - Streaming RPCs (logs, stats, events) use bidirectional channels
- Responses converted back to domain models via
FromProto()methods
Cloud Tool Execution Flow
cloud.Client.Run()blocks untilNotify()signals a cloud dispatcher is configuredconnect()establishes bidirectional gRPC stream (ToolStreamRPC) to cloud endpoint- Cloud sends
ToolRequest(ListTools or CallTool), client dispatches viaexecuteTool() - Tool calls run concurrently (max 5 via weighted semaphore), responses sent back on stream
- On disconnect, exponential backoff (1s→30s with jitter) triggers reconnection
PermissionDeniederrors stop retrying permanently (invalid API key / no pro plan)- Tool definitions cached via
sync.Once; zero overhead for non-cloud users
Log Parsing Pipeline
- Docker API returns multiplexed stream (8-byte headers + payload)
log_reader.goparses headers, extracts stdout/stderr typeevent_generator.goreceives raw log lines- Detection logic identifies:
- JSON structure →
ComplexLogEntry - Multi-line patterns (stack traces) →
GroupedLogEntry - Single lines →
SimpleLogEntry
- JSON structure →
- Log level extraction via regex patterns
LogEventserialized to JSON and sent via SSE- Frontend deserializes and renders with appropriate component
Adding New Features
Adding a New HTTP Route
- Define route in
internal/web/routes.gousing chi router:r.Get("/api/custom-endpoint", h.customHandler) - Implement handler method in appropriate file (e.g.,
actions.go,logs.go) - Use
hostServiceto find container/host viaFindContainer()orFindHost() - Return JSON response or establish SSE/WebSocket stream
Adding a New Log View Type
- Create route file in
assets/pages/(e.g.,custom/[id].vue) - Create composable in
assets/composable/eventStreams.ts(e.g.,useCustomStream()) - Composable should:
- Build API URL with appropriate filters
- Create EventSource connection
- Handle buffering and message batching
- Return reactive
messagesarray and control methods
- Use
LogViewer.vuecomponent to render messages - Add backend API endpoint if needed (see above)
Adding Container Stats/Metrics
- Add field to
Stattype ininternal/container/types.go - Update
stats_collector.goto extract metric from Docker API response - Add calculation logic in
docker/calculation.goif needed - Ensure protobuf definition includes field in
protos/rpc.proto - Frontend automatically receives updates via existing SSE stream
- Update
Containermodel inassets/models/Container.tsif UI needs access
Working with Notifications/Alerts
Backend (internal/notification/):
manager.go: Rule evaluation engine, manages alert statelog_listener.go: Subscribes to container log streams, evaluates rules against incoming logstypes.go: Alert rule definitions (log pattern matching, thresholds)dispatcher/: Notification channel implementations
Frontend (assets/pages/notifications.vue, assets/components/Notification/):
AlertForm.vue,DestinationForm.vue: UI for creating rules- Rules persisted to
./data/notifications.ymlviainternal/notification/persist.go - Alert state displayed in notification cards
Adding a new notification channel:
- Implement dispatcher interface in
internal/notification/dispatcher/ - Register in
manager.godispatcher factory - Add UI form in
assets/components/Notification/DestinationForm.vue
Adding a New Cloud Tool
- Define the tool in
AvailableTools()ininternal/cloud/tools.gowith name, description, and parameter schema - Add a response message type in
protos/cloud.protoand add it to theCallToolResponse.resultoneof - Run
make generateto regenerate protobuf code - Add a case in the
executeTool()switch ininternal/cloud/tools.go - Implement the execution function in the appropriate
tools_*.gofile - Use
ToolHostServiceinterface methods to access container/host data - Add tests in
tools_test.go
Common Development Patterns
Testing
- Always run Go tests with race detector:
go test -race - Frontend tests require
TZ=UTCfor timestamp consistency - Integration tests use Playwright with
make int(runs docker-compose setup) - Use
testify/assertfor Go test assertions
Hot Reload Development
make devruns both backend (air) and frontend (vite) with hot reloadDEV=truedisables embedded asset servingLIVE_FS=trueserves assets from filesystem instead of embedded- Backend changes trigger air restart automatically
- Frontend changes trigger vite HMR
Debugging
- Backend logs: Set
--level debugflag orDOZZLE_LEVEL=debugenv var - Frontend: Vue DevTools browser extension
- SSE streams: Browser DevTools Network tab shows EventSource connections