This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
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
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).
# Install dependencies
pnpm install
# Generate certificates and protobuf files
make generate# 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# Build frontend assets
pnpm build
# or
make dist
# Build entire application (includes frontend build)
make build
# Build Docker image
make docker# 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 production build locally
pnpm preview
# or
make preview
# Run integration tests (Playwright)
make intThe 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/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 supportcontainer/: Container 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.)
-
graph/- GraphQL API layerschema.graphqls: GraphQL schema definitions*.resolvers.go: GraphQL resolver implementations
-
main.go- Application entry point with mode switching (server/swarm/k8s/agent)
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
- 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 - GraphQL: POST to
/api/graphqlfor queries and mutations (container metadata, historical logs, notifications)
- 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
- 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; parent must call exposed
recalculate()to force refresh
- 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
- GraphQL API: Uses gqlgen with schema in
graph/schema.graphqls, generated code ingraph/generated.go- Run
pnpm codegento regenerate GraphQL types - Resolvers follow-schema layout in
graph/*.resolvers.go
- Run
- Service Layer Architecture:
ClientServiceinterface abstracts Docker/K8s/Agent backendsMultiHostServiceorchestrates multi-host operationsClientManagerimplementations:RetriableClientManager(server mode),SwarmClientManager(swarm mode)
- 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
- 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
- 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
dev.dozzle.name: Custom container display namedev.dozzle.group: Group containers together- Label-based filtering throughout the application
- 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
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/container/service.go) - Add HTTP handler in
internal/web/with appropriate route
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
- 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
- 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
- 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
- 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)
- Define in
graph/schema.graphqls - Run
pnpm codegento regenerate types - Implement resolver in
graph/schema.resolvers.go - Use
hostServicefrom resolver context to access backend services - Frontend calls via urql client (auto-imported via
@urql/vue)
- 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
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 stored via GraphQL mutations
- 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 - Add GraphQL schema fields if needed
- 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
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
- Backend logs: Set
--level debugflag orDOZZLE_LEVEL=debugenv var - Frontend: Vue DevTools browser extension
- GraphQL: Use GraphQL Playground at
/api/graphql(when enabled) - SSE streams: Browser DevTools Network tab shows EventSource connections