LogWard¶
LogWard is an open-source, self-hosted log management tool.
Installation¶
Default Port: 3000
Config¶
homelab/docker/logward/.env
# LogWard Docker Configuration
# =============================
# Copy this file to .env and set secure passwords
# =============================================================================
# REQUIRED - Set these before starting
# =============================================================================
# Database password (use a strong random password)
DB_PASSWORD=your_secure_database_password
# Redis password (use a strong random password)
REDIS_PASSWORD=your_secure_redis_password
# API key encryption secret (minimum 32 characters)
API_KEY_SECRET=your_32_character_secret_key_here
# =============================================================================
# OPTIONAL - Customize if needed
# =============================================================================
# Database settings (defaults work for most setups)
DB_NAME=logward
DB_USER=logward
# Frontend API URL (only change if backend is on different host)
# Default: http://localhost:8080
PUBLIC_API_URL=https://logward.l.nicholaswilde.io
# Docker images (pin versions for production stability)
# LOGWARD_BACKEND_IMAGE=logward/backend:0.3.1
# LOGWARD_FRONTEND_IMAGE=logward/frontend:0.3.1
# =============================================================================
# EMAIL NOTIFICATIONS (optional)
# =============================================================================
# SMTP_HOST=smtp.example.com
# SMTP_PORT=587
# [email protected]
# SMTP_PASS=your_smtp_password
# [email protected]
# =============================================================================
# HORIZONTAL SCALING (advanced)
# =============================================================================
# For scaling, use docker-compose.traefik.yml:
# docker compose -f docker-compose.yml -f docker-compose.traefik.yml up -d --scale backend=3
#
# See docs: https://logward.dev/docs/deployment#horizontal-scaling
homelab/docker/logward/compose.yaml
---
# LogWard Docker Compose Configuration
# Basic usage: docker compose up -d
# With Docker log collection: docker compose --profile logging up -d
services:
postgres:
image: timescale/timescaledb:latest-pg16
container_name: logward-postgres
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
command:
- "postgres"
- "-c"
- "max_connections=100"
- "-c"
- "shared_buffers=256MB"
- "-c"
- "effective_cache_size=768MB"
- "-c"
- "work_mem=16MB"
- "-c"
- "maintenance_work_mem=128MB"
# Parallel query settings for faster aggregations
- "-c"
- "max_parallel_workers_per_gather=4"
- "-c"
- "max_parallel_workers=8"
- "-c"
- "parallel_tuple_cost=0.01"
- "-c"
- "parallel_setup_cost=100"
- "-c"
- "min_parallel_table_scan_size=8MB"
# Write-ahead log tuning for ingestion
- "-c"
- "wal_buffers=16MB"
- "-c"
- "checkpoint_completion_target=0.9"
# Logging for slow queries (>100ms)
- "-c"
- "log_min_duration_statement=100"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- logward-network
redis:
image: redis:7-alpine
container_name: logward-redis
command: redis-server --requirepass ${REDIS_PASSWORD}
ports:
- "6379:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "sh", "-c", "redis-cli -a ${REDIS_PASSWORD} ping | grep -q PONG"]
interval: 10s
timeout: 3s
retries: 5
restart: unless-stopped
networks:
- logward-network
backend:
image: ghcr.io/logward-dev/logward-backend:0.3.2-rc1
container_name: logward-backend
ports:
- "8080:8080"
environment:
NODE_ENV: production
DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}
DATABASE_HOST: postgres
DB_USER: ${DB_USER}
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
API_KEY_SECRET: ${API_KEY_SECRET}
PORT: 8080
HOST: 0.0.0.0
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USER: ${SMTP_USER:-}
SMTP_PASS: ${SMTP_PASS:-}
SMTP_FROM: ${SMTP_FROM:[email protected]}
INTERNAL_LOGGING_ENABLED: ${INTERNAL_LOGGING_ENABLED:-false}
INTERNAL_API_KEY: ${INTERNAL_API_KEY:-}
SERVICE_NAME: logward-backend
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:8080/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"]
interval: 30s
timeout: 5s
retries: 3
start_period: 40s
restart: unless-stopped
networks:
- logward-network
worker:
image: ghcr.io/logward-dev/logward-backend:0.3.2-rc1
container_name: logward-worker
command: ["worker"]
healthcheck:
disable: true
environment:
NODE_ENV: production
DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}
DATABASE_HOST: postgres
DB_USER: ${DB_USER}
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
API_KEY_SECRET: ${API_KEY_SECRET}
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USER: ${SMTP_USER:-}
SMTP_PASS: ${SMTP_PASS:-}
SMTP_FROM: ${SMTP_FROM:[email protected]}
INTERNAL_LOGGING_ENABLED: ${INTERNAL_LOGGING_ENABLED:-false}
INTERNAL_API_KEY: ${INTERNAL_API_KEY:-}
SERVICE_NAME: logward-worker
depends_on:
backend:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
networks:
- logward-network
frontend:
image: ghcr.io/logward-dev/logward-frontend:0.3.2-rc1
container_name: logward-frontend
ports:
- "3000:3000"
environment:
NODE_ENV: production
PUBLIC_API_URL: ${PUBLIC_API_URL:-http://localhost:8080}
depends_on:
- backend
restart: unless-stopped
networks:
- logward-network
fluent-bit:
# image: fluent/fluent-bit:latest
image: fluent-bit-16k:latest
container_name: logward-fluent-bit
ports:
- "514:514/udp" # Syslog UDP
- "514:514/tcp" # Syslog TCP
# profiles:
# - logging
volumes:
- ./fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf:ro
- ./parsers.conf:/fluent-bit/etc/parsers.conf:ro
- ./extract_container_id.lua:/fluent-bit/etc/extract_container_id.lua:ro
- ./wrap_logs.lua:/fluent-bit/etc/wrap_logs.lua:ro
- ./map_syslog_level.lua:/fluent-bit/etc/map_syslog_level.lua:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
LOGWARD_API_KEY: ${FLUENT_BIT_API_KEY:-}
LOGWARD_API_HOST: backend
depends_on:
- backend
restart: unless-stopped
networks:
- logward-network
volumes:
postgres_data:
driver: local
redis_data:
driver: local
networks:
logward-network:
Logging¶
To enable automatic log collection from other Docker containers:
- Generate API Key: Log in to LogWard, go to Project Settings, and create an API Key.
-
Update Config: Add the API key to your
.envfile. -
Create Config Files: Ensure the following configuration files exist in your
docker/logwarddirectory.fluent-bit.conf
# Fluent Bit Configuration for LogWard [SERVICE] # Flush logs every 5 seconds Flush 5 # Run in foreground Daemon Off # Log level (error, warning, info, debug, trace) Log_Level info # Parsers configuration file Parsers_File /fluent-bit/etc/parsers.conf # ============================================================================= # INPUT - Docker Container Logs # ============================================================================= [INPUT] Name tail Path /var/lib/docker/containers/*/*.log Parser docker Tag docker.* Refresh_Interval 5 Mem_Buf_Limit 5MB Skip_Long_Lines On Path_Key filepath # ============================================================================= # FILTER - Parse and Enrich # ============================================================================= # Extract container metadata (name, id, image) [FILTER] Name parser Match docker.* Key_Name log Parser docker_json Reserve_Data On Preserve_Key On # Add required fields for LogWard API [FILTER] Name modify Match docker.* # Set default level if not present Add level info # Rename 'log' field to 'message' Rename log message # Set service name from container_name Copy container_name service # Remove unnecessary fields to reduce log size [FILTER] Name record_modifier Match docker.* Remove_key stream Remove_key filepath Remove_key container_name # ============================================================================= # OUTPUT - Send to LogWard # ============================================================================= [OUTPUT] Name http Match docker.* Host ${LOGWARD_API_HOST} Port 8080 URI /api/v1/ingest/single Format json_lines Header X-API-Key ${LOGWARD_API_KEY} Header Content-Type application/json # Date/time settings Json_date_key time Json_date_format iso8601 # Retry settings Retry_Limit 3 # TLS (disable for internal Docker network) tls Offparsers.conf
extract_container_id.lua
-- Extract container ID from Docker log file path -- Path format: /var/lib/docker/containers/<container_id>/<container_id>-json.log function extract_container_id(tag, timestamp, record) local filepath = record["filepath"] if filepath == nil then return 0, timestamp, record end -- Extract container ID from path -- Example: /var/lib/docker/containers/abc123.../abc123...-json.log local container_id = filepath:match("/var/lib/docker/containers/([^/]+)/") if container_id then record["container_id"] = container_id record["container_short_id"] = container_id:sub(1, 12) end -- Return code: 1 = modified and keep, 0 = no change return 1, timestamp, record end -
Enable Logging: Start the stack with the logging profile.
Integrations¶
CLI (curl)¶
You can send a single log entry to LogWard using curl.
:material-proxmox: Proxmox (rsyslog)¶
To forward logs from a Proxmox node (or any Debian-based system) to LogWard via syslog:
-
Install rsyslog (if not already installed):
-
Create configuration file:
-
Restart rsyslog:
Raspberry Pi¶
If you are running on a Raspberry Pi, the standard Fluent Bit docker image might not work because it requires a page size of 4k. The Raspberry Pi kernel often uses a page size of 16k.
To check your page size, run:
If the output is 16384, you must generate a custom Fluent Bit docker image with jemalloc support disabled.
Ensure that your compose.yaml is using the custom image:
Traefik¶
homelab/pve/traefik/conf.d/logward.yaml
---
http:
#region routers
routers:
logward:
entryPoints:
- "websecure"
rule: "Host(`logward.l.nicholaswilde.io`)"
middlewares:
- default-headers@file
- https-redirectscheme@file
tls: {}
service: logward
logward-backend:
entryPoints:
- "websecure"
rule: "Host(`logward.l.nicholaswilde.io`) && (PathPrefix(`/api`) || PathPrefix(`/health`))"
middlewares:
- default-headers@file
- https-redirectscheme@file
tls: {}
service: logward-backend
#endregion
#region services
services:
logward:
loadBalancer:
servers:
- url: "http://192.168.2.195:3000"
passHostHeader: true
logward-backend:
loadBalancer:
servers:
- url: "http://192.168.2.195:8080"
passHostHeader: true
healthCheck:
path: /health
interval: 10s
#endregion
middlewares:
https-redirectscheme:
redirectScheme:
scheme: https
permanent: true
default-headers:
headers:
frameDeny: true
browserXssFilter: true
contentTypeNosniff: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 15552000
customFrameOptionsValue: SAMEORIGIN
customRequestHeaders:
X-Forwarded-Proto: https
default-whitelist:
ipAllowList:
sourceRange:
- "10.0.0.0/8"
- "192.168.0.0/16"
- "172.16.0.0/12"
secured:
chain:
middlewares:
- default-whitelist
- default-headers
Troubleshooting¶
If you are experiencing issues with log collection, follow these steps:
-
Verify Fluent Bit is running:
-
Check Fluent Bit logs:
-
Test connectivity:
Verify you can reach LogWard's Fluent Bit port (514) from your source device.
-
Verify API Key:
Ensure
FLUENT_BIT_API_KEYis correctly set in your.envfile. -
Test Syslog Manually:
Send a test message to verify the pipeline.