2637 字
13 分钟

Complete Deployment Guide to Dokploy Security & Monitoring: CrowdSec + Traefik + Grafana

This is the third article in the Dokploy series! Here are the security rules and configurations I use.

This article documents the practical experience of building a complete security and monitoring system in a Dokploy environment, covering CrowdSec threat detection, Traefik and Cloudflare integration, and an observability platform based on Grafana/Loki/VictoriaMetrics. All configurations can be directly replicated.

Dokploy Article Series:

Architecture Overview#

The entire system consists of three core modules:

Dokploy Security and Monitoring Architecture

┌─────────────────────────────────────────────────────────────────────────┐
│ Internet │
│ User Request ──► Cloudflare CDN ──► Traefik (:80/:443) │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────┴─────────────────┐
▼ ▼
┌───────────────────┐ ┌─────────────────┐
│ Traefik Middleware│ │ Access Log │
│ ┌───────────────┐ │ │ (JSON format) │
│ │Cloudflare-Only│ │ └────────┬────────┘
│ │ IP Allowlist │ │ │
│ └───────────────┘ │ ▼
│ ┌───────────────┐ │ ┌─────────────────┐
│ │ crowdsec │◄├─────────────────│ CrowdSec │
│ │Threat Detection│ │ │ Engine │
│ └───────────────┘ │ └────────┬────────┘
└─────────┬─────────┘ │
│ ┌──────────────┼──────────────┐
▼ ▼ ▼ ▼
┌─────────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Docker Containers│ │ Firewall │ │ Traefik │ │VictoriaMetrics│
│ App Services │ │ Bouncer │ │ Bouncer │ │ Alerts │
└─────────────────┘ │ (nftables) │ │ (Plugin) │ └─────────────┘
└─────────────┘ └─────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ Monitoring Stack │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Alloy │───►│ Loki │───►│ Grafana │ │
│ │ Log Collector│ │ Log Storage│ │Visualization│ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ ┌─────────────┐ ┌─────────────┐ │ │
│ │Node Exporter│───►│VictoriaMetrics│─────────┘ │
│ │ Host Metrics│ │Metrics Storage│ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘

Component Responsibilities#

ComponentResponsibilityData Retention
CrowdSec EngineThreat detection engine, analyzes logs to identify malicious behavior-
Firewall BouncerHost-level firewall, blocks malicious IPs using nftables-
Traefik BouncerApplication-level protection, blocks requests in real-time-
LokiLog aggregation and storage14 days
VictoriaMetricsTime-series metrics storage90 days
AlloyLog collector, supports multiple log sources-
Node ExporterHost performance metrics collection-
GrafanaUnified visualization dashboard-

Part 1: CrowdSec Security Protection#

1.1 Introduction to CrowdSec#

CrowdSec is an open-source collaborative security engine that detects malicious behavior by analyzing logs and executes ban decisions through Bouncers. Its features include:

  • Behavior Analysis: Scenario-based threat detection
  • Community Sharing: Global threat intelligence sharing
  • Multi-layer Protection: Supports both firewall and application-layer Bouncers
  • Lightweight & Efficient: Written in Go, low resource consumption

1.2 Deploying CrowdSec in Dokploy#

Create a Compose service with the following configuration:

docker-compose.yml
services:
crowdsec:
image: crowdsecurity/crowdsec:latest
environment:
GID: "${GID-1000}"
COLLECTIONS: "crowdsecurity/linux crowdsecurity/traefik crowdsecurity/http-cve"
volumes:
- crowdsec-db:/var/lib/crowdsec/data/
- crowdsec-config:/etc/crowdsec/
- ../files/acquis.yaml:/etc/crowdsec/acquis.yaml
- ../files/victoriametrics.yaml:/etc/crowdsec/notifications/http_victoriametrics.yaml
- ../files/profiles.yaml:/etc/crowdsec/profiles.yaml
- /etc/dokploy/traefik/dynamic/access.log:/var/log/traefik/access.log:ro
- /var/log/auth.log:/var/log/ssh/auth.log:ro
security_opt:
- no-new-privileges:true
ports:
- "127.0.0.1:8080:8080" # LAPI
- "127.0.0.1:6060:6060" # Prometheus metrics
labels:
- traefik.enable=false
restart: unless-stopped
networks:
- default
- dokploy-network
networks:
default:
dokploy-network:
external: true
volumes:
crowdsec-db:
crowdsec-config:

Key Configuration Notes:

  • COLLECTIONS: Detection collections enabled, including Linux system, Traefik Web, and HTTP CVE
  • Mount Traefik access logs for CrowdSec analysis
  • Mount SSH auth logs to detect brute force attacks
  • Ports bound only to 127.0.0.1 for enhanced security

1.3 Log Acquisition Configuration#

Create files/acquis.yaml to define log sources for CrowdSec to analyze:

acquis.yaml
---
# Traefik access log acquisition
filenames:
- /var/log/traefik/access.log
poll_without_inotify: true
labels:
type: traefik
---
# SSH auth log acquisition
filenames:
- /var/log/ssh/auth.log
labels:
type: syslog

1.4 Decision Configuration#

Create files/profiles.yaml to define ban policies:

profiles.yaml
name: default_ip_remediation
filters:
- Alert.Remediation == true && Alert.GetScope() == "Ip"
decisions:
- type: ban
duration: 4h
notifications:
- http_victoriametrics
on_success: break
---
name: default_range_remediation
filters:
- Alert.Remediation == true && Alert.GetScope() == "Range"
decisions:
- type: ban
duration: 4h
notifications:
- http_victoriametrics
on_success: break

Configuration Notes:

  • Ban malicious IPs for 4 hours upon detection
  • Supports both single IP and IP range bans
  • Each decision is notified to VictoriaMetrics for monitoring

1.5 Host-level Firewall Bouncer#

Install the Firewall Bouncer on the host to implement firewall-level blocking using nftables:

Terminal window
# Installation
curl -s https://install.crowdsec.net | sudo sh
apt install crowdsec-firewall-bouncer-nftables

Configuration file /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml:

mode: nftables
update_frequency: 10s
log_mode: file
log_dir: /var/log/
log_level: info
log_compression: true
log_max_size: 100
log_max_backups: 3
log_max_age: 30
api_url: http://127.0.0.1:8080/
api_key: YOUR_API_KEY_HERE
insecure_skip_verify: false
disable_ipv6: false
deny_action: DROP
deny_log: false
supported_decisions_types:
- ban
blacklists_ipv4: crowdsec-blacklists
blacklists_ipv6: crowdsec6-blacklists
ipset_type: nethash
iptables_chains:
- INPUT
iptables_add_rule_comments: true
nftables:
ipv4:
enabled: true
set-only: false
table: crowdsec
chain: crowdsec-chain
priority: -10
ipv6:
enabled: true
set-only: false
table: crowdsec6
chain: crowdsec6-chain
priority: -10
nftables_hooks:
- input
- forward
prometheus:
enabled: false
listen_addr: 127.0.0.1
listen_port: 60601

Getting the API Key:

Terminal window
# Execute in CrowdSec container
docker exec -it <crowdsec-container> cscli bouncers add firewall-bouncer

1.6 Traefik Bouncer Plugin#

The Traefik Bouncer implements real-time request filtering at the application layer. First, enable the plugin in traefik.yml:

experimental:
plugins:
bouncer:
moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
version: v1.4.6

Then use it in middleware configuration:

http:
middlewares:
crowdsec:
plugin:
bouncer:
enabled: true
crowdsecMode: live
crowdsecLapiKey: YOUR_TRAEFIK_BOUNCER_KEY
crowdsecLapiHost: service-crowdsec-k5ws6c-crowdsec-1:8080

Getting the Traefik Bouncer Key:

Terminal window
docker exec -it <crowdsec-container> cscli bouncers add traefik-bouncer

1.7 VictoriaMetrics Alert Notification#

Create files/victoriametrics.yaml to push CrowdSec decisions to VictoriaMetrics:

type: http
name: http_victoriametrics
log_level: debug
format: >
{{- range $Alert := . -}}
{{- $traefikRouters := GetMeta . "traefik_router_name" -}}
{{- range .Decisions -}}
{"metric":{"__name__":"cs_lapi_decision","instance":"dokploy-server","country":"{{$Alert.Source.Cn}}","asname":"{{$Alert.Source.AsName}}","asnumber":"{{$Alert.Source.AsNumber}}","latitude":"{{$Alert.Source.Latitude}}","longitude":"{{$Alert.Source.Longitude}}","iprange":"{{$Alert.Source.Range}}","scenario":"{{.Scenario}}","type":"{{.Type}}","duration":"{{.Duration}}","scope":"{{.Scope}}","ip":"{{.Value}}","traefik_routers":{{ printf "%q" ($traefikRouters | uniq | join ",")}}},"values": [1],"timestamps":[{{now|unixEpoch}}000]}
{{- end }}
{{- end -}}
url: http://service-victoriametrics-dhydty-victoriametrics-1:8428/api/v1/import
method: POST
headers:
Content-Type: application/json

This creates the cs_lapi_decision metric containing attacker’s geolocation, ASN, scenario, and other information for analysis in Grafana.

Part 2: Traefik and Cloudflare Integration#

2.1 Why Cloudflare IP Allowlist is Needed#

When using Cloudflare as a CDN, all requests pass through Cloudflare proxies. This creates two issues:

  1. Source IP Confusion: Traefik sees Cloudflare’s proxy IP, not the real user IP
  2. Direct Access Bypass: Attackers might directly access the origin server IP, bypassing Cloudflare’s protection

Solution:

  • Configure forwardedHeaders.trustedIPs to trust Cloudflare IPs and correctly obtain real client IPs
  • Configure ipAllowList middleware to only allow Cloudflare IPs

2.2 Traefik Static Configuration#

Complete /etc/dokploy/traefik/traefik.yml configuration:

global:
sendAnonymousUsage: false
experimental:
plugins:
bouncer:
moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
version: v1.4.6
accessLog:
filePath: "/etc/dokploy/traefik/dynamic/access.log"
format: json
bufferingSize: 100
providers:
swarm:
exposedByDefault: false
watch: true
docker:
exposedByDefault: false
watch: true
file:
directory: /etc/dokploy/traefik/dynamic
watch: true
entryPoints:
web:
address: :80
forwardedHeaders:
trustedIPs:
# Cloudflare IPv4
- 173.245.48.0/20
- 103.21.244.0/22
- 103.22.200.0/22
- 103.31.4.0/22
- 141.101.64.0/18
- 108.162.192.0/18
- 190.93.240.0/20
- 188.114.96.0/20
- 197.234.240.0/22
- 198.41.128.0/17
- 162.158.0.0/15
- 104.16.0.0/13
- 104.24.0.0/14
- 172.64.0.0/13
- 131.0.72.0/22
http:
middlewares:
- cloudflare-only@file
- crowdsec@file
websecure:
address: :443
http3:
advertisedPort: 443
forwardedHeaders:
trustedIPs:
# Cloudflare IPv4 (same as web)
- 173.245.48.0/20
- 103.21.244.0/22
- 103.22.200.0/22
- 103.31.4.0/22
- 141.101.64.0/18
- 108.162.192.0/18
- 190.93.240.0/20
- 188.114.96.0/20
- 197.234.240.0/22
- 198.41.128.0/17
- 162.158.0.0/15
- 104.16.0.0/13
- 104.24.0.0/14
- 172.64.0.0/13
- 131.0.72.0/22
http:
middlewares:
- cloudflare-only@file
- crowdsec@file
tls:
certResolver: letsencrypt
api:
insecure: true
certificatesResolvers:
letsencrypt:
acme:
email: your-email@example.com
storage: /etc/dokploy/traefik/dynamic/acme.json
httpChallenge:
entryPoint: web

Key Configuration Notes:

  • forwardedHeaders.trustedIPs: Trust Cloudflare IPs so Traefik correctly parses the X-Forwarded-For header to get real client IPs
  • http.middlewares: Entry-point level middleware chain, all requests pass through cloudflare-only and crowdsec middlewares
  • accessLog.format: json: JSON format logs for easy parsing by CrowdSec and Alloy

2.3 Dynamic Middleware Configuration#

/etc/dokploy/traefik/dynamic/middlewares.yml:

http:
middlewares:
redirect-to-https:
redirectScheme:
scheme: https
permanent: true
cloudflare-only:
ipAllowList:
sourceRange:
# Cloudflare IPv4
- "173.245.48.0/20"
- "103.21.244.0/22"
- "103.22.200.0/22"
- "103.31.4.0/22"
- "141.101.64.0/18"
- "108.162.192.0/18"
- "190.93.240.0/20"
- "188.114.96.0/20"
- "197.234.240.0/22"
- "198.41.128.0/17"
- "162.158.0.0/15"
- "104.16.0.0/13"
- "104.24.0.0/14"
- "172.64.0.0/13"
- "131.0.72.0/22"
# Cloudflare IPv6
- "2400:cb00::/32"
- "2606:4700::/32"
- "2803:f800::/32"
- "2405:b500::/32"
- "2405:8100::/32"
- "2a06:98c0::/29"
- "2c0f:f248::/32"
crowdsec:
plugin:
bouncer:
enabled: true
crowdsecMode: live
crowdsecLapiKey: YOUR_TRAEFIK_BOUNCER_KEY
crowdsecLapiHost: service-crowdsec-k5ws6c-crowdsec-1:8080
TIP

Cloudflare IP lists may update. It’s recommended to periodically fetch the latest list from https://www.cloudflare.com/ips/.

2.4 Real IP Recognition Mechanism#

When requests flow through the following path, IP information changes:

User (203.0.113.50)
→ Cloudflare (adds CF-Connecting-IP: 203.0.113.50)
→ Traefik (trusts Cloudflare IP, parses X-Forwarded-For)
→ CrowdSec (gets real IP 203.0.113.50 from logs)

Traefik access log example:

{
"ClientAddr": "172.71.99.180:11150",
"ClientHost": "203.0.113.50",
"RequestHost": "example.com",
"RequestMethod": "GET",
"RequestPath": "/api/data",
"DownstreamStatus": 200,
"RouterName": "my-router@docker"
}
  • ClientAddr: Cloudflare proxy IP
  • ClientHost: Real client IP (correctly parsed through trust configuration)

2.5 Special Route Bypass Configuration#

Some services may need to bypass CrowdSec detection (like analytics services). Create a separate route configuration:

rybbit-bypass.yml
http:
routers:
rybbit-backend-websecure:
rule: Host(`analytics.example.com`) && PathPrefix(`/api`)
service: rybbit-backend-service
entryPoints:
- websecure
tls:
certResolver: letsencrypt
priority: 100 # High priority ensures matching first
services:
rybbit-backend-service:
loadBalancer:
servers:
- url: http://rybbit-backend:3001
passHostHeader: true

Note: These routes don’t have the crowdsec@file middleware applied, so they’re not protected by CrowdSec.

Part 3: Monitoring and Logging System#

3.1 Loki Log Storage#

Loki is a log aggregation system developed by Grafana Labs, designed for Kubernetes and container environments.

files/loki-config.yaml configuration:

# Loki 3.x configuration - Storage optimized
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
log_level: info
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
# Loki 3.x uses TSDB + v13 schema
schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
storage_config:
tsdb_shipper:
active_index_directory: /loki/tsdb-shipper-active
cache_location: /loki/tsdb-shipper-cache
cache_ttl: 24h
filesystem:
directory: /loki/chunks
# Ingester optimization for better compression
ingester:
chunk_idle_period: 30m
chunk_block_size: 262144 # 256KB blocks
chunk_target_size: 1572864 # 1.5MB chunks
chunk_retain_period: 1m
max_chunk_age: 2h
flush_check_period: 30s
flush_op_timeout: 10m
wal:
enabled: true
dir: /loki/wal
flush_on_shutdown: true
limits_config:
retention_period: 336h # 14 days retention
ingestion_rate_mb: 8
ingestion_burst_size_mb: 16
max_streams_per_user: 5000
max_entries_limit_per_query: 5000
max_line_size: 128KB
allow_structured_metadata: true
per_stream_rate_limit: 3MB
per_stream_rate_limit_burst: 10MB
max_label_name_length: 1024
max_label_value_length: 2048
max_label_names_per_series: 15
compactor:
working_directory: /loki/compactor
retention_enabled: true
retention_delete_delay: 1h
delete_request_store: filesystem
compaction_interval: 10m
retention_delete_worker_count: 150
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
parallelise_shardable_queries: true
cache_results: true
query_scheduler:
max_outstanding_requests_per_tenant: 100
analytics:
reporting_enabled: false

3.2 Alloy Log Collection#

Grafana Alloy is the next-generation log/metrics collector, replacing Promtail.

files/alloy-config.alloy configuration:

// Grafana Alloy Configuration - Log Collection
// Optimized for storage efficiency
// ============================================
// Loki Output Endpoint
// ============================================
loki.write "default" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
batch_wait = "5s" // Batch for better compression
batch_size = "1MiB" // 1MB batch size
}
external_labels = {
cluster = "dokploy",
}
}
// ============================================
// Docker Container Discovery
// ============================================
discovery.docker "containers" {
host = "unix:///var/run/docker.sock"
refresh_interval = "30s"
}
// ============================================
// Docker Container Log Collection
// ============================================
discovery.relabel "docker_logs" {
targets = discovery.docker.containers.targets
// Extract container name
rule {
source_labels = ["__meta_docker_container_name"]
regex = "/(.*)"
target_label = "container"
}
// Extract compose project
rule {
source_labels = ["__meta_docker_container_label_com_docker_compose_project"]
target_label = "compose_project"
}
// Extract compose service
rule {
source_labels = ["__meta_docker_container_label_com_docker_compose_service"]
target_label = "compose_service"
}
// Extract image name
rule {
source_labels = ["__meta_docker_container_image"]
target_label = "image"
}
}
loki.source.docker "containers" {
host = "unix:///var/run/docker.sock"
targets = discovery.relabel.docker_logs.output
forward_to = [loki.process.docker.receiver]
}
loki.process "docker" {
stage.static_labels {
values = {
job = "docker",
host = "dokploy-server",
}
}
forward_to = [loki.write.default.receiver]
}
// ============================================
// Traefik Access Log (JSON format)
// ============================================
local.file_match "traefik" {
path_targets = [{"__path__" = "/var/log/traefik/access.log"}]
sync_period = "15s"
}
loki.source.file "traefik" {
targets = local.file_match.traefik.targets
forward_to = [loki.process.traefik.receiver]
tail_from_end = true
}
loki.process "traefik" {
stage.static_labels {
values = {
job = "traefik",
host = "dokploy-server",
}
}
// Parse JSON and extract fields as labels
stage.json {
expressions = {
client_ip = "ClientHost",
method = "RequestMethod",
path = "RequestPath",
status = "DownstreamStatus",
router = "RouterName",
request_host = "RequestHost",
entry_point = "entryPointName",
}
}
stage.labels {
values = {
client_ip = "",
method = "",
status = "",
router = "",
request_host = "",
entry_point = "",
}
}
forward_to = [loki.write.default.receiver]
}
// ============================================
// SSH/Auth Log
// ============================================
local.file_match "auth" {
path_targets = [{"__path__" = "/var/log/host/auth.log"}]
sync_period = "30s"
}
loki.source.file "auth" {
targets = local.file_match.auth.targets
forward_to = [loki.process.auth.receiver]
tail_from_end = true
}
loki.process "auth" {
stage.static_labels {
values = {
job = "auth",
host = "dokploy-server",
}
}
// Parse syslog format
stage.regex {
expression = "^(?P<timestamp>\\w+\\s+\\d+\\s+\\d+:\\d+:\\d+)\\s+(?P<hostname>\\S+)\\s+(?P<service>[^\\[:]+)(?:\\[(?P<pid>\\d+)\\])?:\\s+(?P<message>.*)$"
}
stage.labels {
values = {
service = "",
}
}
forward_to = [loki.write.default.receiver]
}
// ============================================
// UFW Firewall Log
// ============================================
local.file_match "ufw" {
path_targets = [{"__path__" = "/var/log/host/ufw.log"}]
sync_period = "30s"
}
loki.source.file "ufw" {
targets = local.file_match.ufw.targets
forward_to = [loki.process.ufw.receiver]
tail_from_end = true
}
loki.process "ufw" {
stage.static_labels {
values = {
job = "ufw",
host = "dokploy-server",
}
}
// Extract UFW fields
stage.regex {
expression = "\\[UFW (?P<action>\\w+)\\].*SRC=(?P<src_ip>[\\d\\.a-fA-F:]+).*PROTO=(?P<proto>\\w+)"
}
stage.regex {
expression = "DPT=(?P<dst_port>\\d+)"
}
stage.labels {
values = {
action = "",
src_ip = "",
proto = "",
dst_port = "",
}
}
forward_to = [loki.write.default.receiver]
}
// ============================================
// Kernel Log
// ============================================
local.file_match "kernel" {
path_targets = [{"__path__" = "/var/log/host/kern.log"}]
sync_period = "30s"
}
loki.source.file "kernel" {
targets = local.file_match.kernel.targets
forward_to = [loki.process.kernel.receiver]
tail_from_end = true
}
loki.process "kernel" {
stage.static_labels {
values = {
job = "kernel",
host = "dokploy-server",
}
}
forward_to = [loki.write.default.receiver]
}
// ============================================
// CrowdSec Logs
// ============================================
local.file_match "crowdsec" {
path_targets = [
{"__path__" = "/var/log/crowdsec/crowdsec.log", "log_type" = "main"},
{"__path__" = "/var/log/crowdsec/crowdsec_api.log", "log_type" = "api"},
{"__path__" = "/var/log/crowdsec/firewall-bouncer.log", "log_type" = "firewall-bouncer"},
]
sync_period = "30s"
}
loki.source.file "crowdsec" {
targets = local.file_match.crowdsec.targets
forward_to = [loki.process.crowdsec.receiver]
tail_from_end = true
}
loki.process "crowdsec" {
stage.static_labels {
values = {
job = "crowdsec",
host = "dokploy-server",
}
}
forward_to = [loki.write.default.receiver]
}

3.3 VictoriaMetrics Metrics Storage#

VictoriaMetrics is a high-performance Prometheus-compatible time-series database.

docker-compose.yml:

version: "3.8"
services:
victoriametrics:
image: victoriametrics/victoria-metrics:latest
restart: unless-stopped
volumes:
- vm-data:/storage
- ../files/scrape.yml:/etc/victoriametrics/scrape.yml
- ../files/blackbox:/etc/prometheus/blackbox
ports:
- "127.0.0.1:20001:8428"
command:
- "--storageDataPath=/storage"
- "--retentionPeriod=90d"
- "--httpListenAddr=:8428"
- "--promscrape.config=/etc/victoriametrics/scrape.yml"
networks:
- default
- dokploy-network
networks:
default:
dokploy-network:
external: true
volumes:
vm-data:

files/scrape.yml metrics collection configuration:

scrape_configs:
- job_name: 'crowdsec'
scrape_interval: 15s
static_configs:
- targets: ['service-crowdsec-k5ws6c-crowdsec-1:6060']
- job_name: 'logporter'
scrape_interval: 15s
static_configs:
- targets: ['service-loki-wykkft-logporter-1:9333']
metric_relabel_configs:
- source_labels: [__name__]
regex: 'docker_.*'
action: keep
- job_name: 'node-exporter'
scrape_interval: 15s
static_configs:
- targets: ['service-loki-wykkft-node_exporter-1:9100']

3.4 Complete Docker Compose Configuration#

Complete configuration for Loki + Alloy + Node Exporter + Logporter:

version: "3.8"
services:
loki:
image: grafana/loki:latest
restart: unless-stopped
command: -config.file=/etc/loki/local-config.yaml
volumes:
- loki-data:/loki
- ../files/loki-config.yaml:/etc/loki/local-config.yaml
networks:
- default
- dokploy-network
alloy:
image: grafana/alloy:latest
restart: unless-stopped
command:
- run
- /etc/alloy/config.alloy
- --server.http.listen-addr=0.0.0.0:12345
- --storage.path=/var/lib/alloy/data
volumes:
- ../files/alloy-config.alloy:/etc/alloy/config.alloy
- alloy-data:/var/lib/alloy/data
- /var/run/docker.sock:/var/run/docker.sock:ro
# Traefik logs
- /etc/dokploy/traefik/dynamic/access.log:/var/log/traefik/access.log:ro
# System logs
- /var/log/auth.log:/var/log/host/auth.log:ro
- /var/log/ufw.log:/var/log/host/ufw.log:ro
- /var/log/kern.log:/var/log/host/kern.log:ro
# CrowdSec logs
- /var/log/crowdsec.log:/var/log/crowdsec/crowdsec.log:ro
- /var/log/crowdsec_api.log:/var/log/crowdsec/crowdsec_api.log:ro
- /var/log/crowdsec-firewall-bouncer.log:/var/log/crowdsec/firewall-bouncer.log:ro
depends_on:
- loki
networks:
- default
- dokploy-network
logporter:
image: lifailon/logporter:latest
restart: unless-stopped
environment:
- DOCKER_LOG_METRICS=false
- DOCKER_LOG_CUSTOM_METRICS=false
- DOCKER_LOG_CUSTOM_QUERY=error|Error|ERROR|exception|Exception|EXCEPTION|fail|Fail|FAIL
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- default
- dokploy-network
node_exporter:
image: quay.io/prometheus/node-exporter:latest
restart: unless-stopped
command:
- '--path.rootfs=/host'
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/host:ro,rslave
pid: host
networks:
- default
- dokploy-network
networks:
default:
dokploy-network:
external: true
volumes:
loki-data:
alloy-data:

3.5 Grafana Deployment#

Grafana is used for unified visualization of all monitoring data:

version: "3.8"
services:
grafana:
image: grafana/grafana:latest
restart: unless-stopped
volumes:
- grafana-storage:/var/lib/grafana
ports:
- "127.0.0.1:20000:3000"
networks:
- default
- dokploy-network
networks:
default:
dokploy-network:
external: true
volumes:
grafana-storage:

Configuring Data Sources:

  1. Loki: Set URL to http://service-loki-wykkft-loki-1:3100
  2. VictoriaMetrics: Set URL to http://service-victoriametrics-dhydty-victoriametrics-1:8428

Dashboard Screenshots#

image-20251222161441409

image-20251222161612086

image-20251222161656811

image-20251222161936331

Part 4: Troubleshooting#

4.1 CrowdSec Common Issues#

Issue: Bouncer connection failed

Terminal window
# Check CrowdSec service status
docker exec -it <crowdsec-container> cscli bouncers list
# Check LAPI connection
curl -s http://127.0.0.1:8080/v1/decisions | head

Issue: No attacks detected

Terminal window
# Check log acquisition status
docker exec -it <crowdsec-container> cscli metrics
# Check scenario status
docker exec -it <crowdsec-container> cscli scenarios list

Issue: False positive blocking legitimate users

Terminal window
# View current ban list
docker exec -it <crowdsec-container> cscli decisions list
# Remove specific IP ban
docker exec -it <crowdsec-container> cscli decisions delete --ip <IP>
# Add whitelist
docker exec -it <crowdsec-container> cscli parsers install crowdsecurity/whitelists

4.2 Traefik Connection Issues#

Issue: 503 Service Unavailable

Terminal window
# Check container network
docker network inspect dokploy-network
# Check if service is in correct network
docker inspect <container> | grep -A 10 Networks

Issue: Real IP not obtained correctly

Check if forwardedHeaders.trustedIPs in traefik.yml includes all Cloudflare IPs.

4.3 Log Collection Issues#

Issue: No logs in Loki

Terminal window
# Check Alloy status
docker logs <alloy-container> --tail 100
# Check Loki health status
curl -s http://127.0.0.1:3100/ready

Issue: Missing log labels

Check that stage.labels and stage.json configurations in Alloy config are correct.

4.4 Missing Monitoring Data#

Issue: No data in VictoriaMetrics

Terminal window
# Check scrape target status
curl -s http://127.0.0.1:20001/targets
# Manually query metrics
curl -s "http://127.0.0.1:20001/api/v1/query?query=up"

Issue: CrowdSec metrics push failed

Check if the URL in victoriametrics.yaml is correct and ensure container names match actual names.

Summary#

This article detailed the process of building a complete security and monitoring system in a Dokploy environment:

  1. CrowdSec Multi-layer Protection: Containerized deployment + Host Firewall Bouncer + Traefik Plugin
  2. Traefik + Cloudflare Integration: IP Allowlist + Real IP Recognition
  3. Observability Platform: Loki Logs + VictoriaMetrics Metrics + Grafana Visualization
  4. Comprehensive Log Collection: Docker containers, system logs, Traefik access logs, security logs

This system provides complete protection from network layer to application layer, along with comprehensive observability support, suitable for production environments.


References:

Complete Deployment Guide to Dokploy Security & Monitoring: CrowdSec + Traefik + Grafana
https://catcat.blog/en/2025/12/dokploy-security-monitoring-guideen/
作者
猫猫博客
发布于
2025-12-22
许可协议
CC BY-NC-SA 4.0