Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
76b3dc1
Add Prometheus monitoring for Caddy in production
claude Nov 6, 2025
9db41b7
Add monitoring for API and PostgreSQL services
claude Nov 6, 2025
703d046
Expose Caddy admin API on localhost for debugging
claude Nov 6, 2025
2477cdb
Add Grafana dashboard for metrics visualization
claude Nov 6, 2025
e2bdfa8
Add local testing setup for monitoring stack
claude Nov 6, 2025
c5af2bd
Add Makefile targets for monitoring stack management
claude Nov 6, 2025
6118d63
Add Docker compose commands and container management targets
claude Nov 6, 2025
bb18d44
Replace colons with hyphens in monitoring target names
claude Nov 6, 2025
0a12db4
Add dashboard export tool and comprehensive generation guide
claude Nov 6, 2025
9ac97a2
Move export script from scripts/ to grafana/ directory
claude Nov 6, 2025
252d145
Reorganize monitoring files into module-specific locations
claude Nov 6, 2025
4031b95
Fix postgres_exporter Docker secrets handling
claude Nov 7, 2025
7d6db9a
Fix critical Caddy admin API security vulnerability
claude Nov 7, 2025
36bd9f2
Replace inline command with proper entrypoint script for postgres_exp…
claude Nov 7, 2025
d5028ac
Remove insecure default Grafana admin password
claude Nov 7, 2025
5f15da7
Fix Caddy dashboard metrics to match official Caddy /metrics endpoint
claude Nov 7, 2025
40bba20
Fix dashboard filename collision risk in export script
claude Nov 7, 2025
a84986d
Add error handling for dashboard export failures
claude Nov 7, 2025
c32559c
Add input validation for dashboard selection
claude Nov 7, 2025
84d88fb
go.sum
gocanto Nov 7, 2025
b1213ae
Fix shell portability: Replace bash-specific brace expansion with seq
claude Nov 7, 2025
c8f0b15
Fix environment label inconsistency in prometheus.local.yml
claude Nov 7, 2025
fb5b234
Enable metrics directive in Caddy admin API configuration
claude Nov 7, 2025
ee8ae52
Improve error diagnostics in postgres-exporter entrypoint script
claude Nov 7, 2025
7d7ffdd
Add language identifier to directory structure code block
claude Nov 7, 2025
40c9786
Add production traffic targets and backup rotation to monitor.mk
claude Nov 7, 2025
7d7a248
Fix Prometheus targets to use container DNS names
claude Nov 7, 2025
2385e8a
Update directory structure diagram to match actual dashboard filenames
claude Nov 7, 2025
d1950f7
wip
gocanto Nov 10, 2025
d3da310
Fix xargs portability issue for macOS/BSD compatibility
claude Nov 10, 2025
1011725
URL-encode credentials in PostgreSQL DSN
claude Nov 10, 2025
c989278
Combine DASHBOARD_GUIDE.md into README.md
claude Nov 10, 2025
9867330
Fix read command to prevent backslash mangling
claude Nov 10, 2025
8d69f5d
Add comprehensive Ubuntu VPS deployment guide for Hostinger
claude Nov 10, 2025
3500ed2
Add .PHONY declarations to monitor.mk
claude Nov 10, 2025
efa37f1
Make /metrics endpoint POST-only and require authentication
claude Nov 10, 2025
36f10a7
Reorganize monitoring stack into monitoring/ directory
claude Nov 10, 2025
dbac4b3
Refactor monitor.mk and app.mk: remove hardcoded values and standardi…
claude Nov 10, 2025
4201a37
Consolidate monitoring documentation and update backup directory
claude Nov 10, 2025
c2fccb2
Move monitoring directory into infra root directory
claude Nov 10, 2025
64d86ab
Reorganize infrastructure directories
claude Nov 10, 2025
818a21e
Move caddy directory to infra/caddy
claude Nov 10, 2025
9d12245
Fix remaining caddy path references
claude Nov 10, 2025
4f3841c
Fix container paths in caddy-validate command
claude Nov 10, 2025
0465e44
Add missing environment variables to .env.example
claude Nov 10, 2025
a1ac2e4
work on local fixes
gocanto Nov 10, 2025
38c8efe
metrics endpoint
gocanto Nov 10, 2025
afd0897
fix caddy dashboards
gocanto Nov 10, 2025
3b5d07e
Change /metrics endpoint to use network isolation instead of auth
claude Nov 11, 2025
6543379
fix local build
gocanto Nov 11, 2025
5683576
wip
gocanto Nov 11, 2025
c53ca7e
consistency
gocanto Nov 11, 2025
2d55a68
enhancements
gocanto Nov 11, 2025
330df79
docs
gocanto Nov 11, 2025
711673b
tweaks
gocanto Nov 11, 2025
35ebd1e
fix metrics endpoint
gocanto Nov 12, 2025
e0766fd
consistency
gocanto Nov 12, 2025
66caac2
localhost
gocanto Nov 12, 2025
b6f9096
wip
gocanto Nov 12, 2025
4de2096
wip
gocanto Nov 12, 2025
c8ce9a0
helpers
gocanto Nov 12, 2025
9318ecd
docs
gocanto Nov 12, 2025
6b92a9a
wip
gocanto Nov 12, 2025
be42cad
backups target
gocanto Nov 12, 2025
94a9ad2
wip
gocanto Nov 12, 2025
e4d1276
wip
gocanto Nov 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,13 @@ ENV_DOCKER_USER_GROUP="ggroup"
ENV_PING_USERNAME=
ENV_PING_PASSWORD=

# --- HTTP Server
ENV_HTTP_PORT=8080

# --- SEO: SPA application directory
ENV_SPA_DIR=
ENV_SPA_IMAGES_DIR=

# --- Monitoring: Grafana admin password
# REQUIRED for Grafana dashboard access
GRAFANA_ADMIN_PASSWORD=
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ storage/seo/*.*
!storage/seo/.gitkeep

# --- [Caddy]: mtls
caddy/mtls/*.*
!caddy/mtls/.gitkeep
infra/caddy/mtls/*.*
!infra/caddy/mtls/.gitkeep

# --- [API]: Bin
bin/*
Expand Down
27 changes: 18 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@ CGO_ENABLED := 1
# -------------------------------------------------------------------------------------------------------------------- #
# -------------------------------------------------------------------------------------------------------------------- #

include ./metal/makefile/helpers.mk
include ./metal/makefile/env.mk
include ./metal/makefile/db.mk
include ./metal/makefile/app.mk
include ./metal/makefile/logs.mk
include ./metal/makefile/build.mk
include ./metal/makefile/infra.mk
include ./metal/makefile/caddy.mk
include ./infra/makefile/helpers.mk
include ./infra/makefile/env.mk
include ./infra/makefile/db.mk
include ./infra/makefile/app.mk
include ./infra/makefile/logs.mk
include ./infra/makefile/build.mk
include ./infra/makefile/infra.mk
include ./infra/makefile/caddy.mk
include ./infra/makefile/monitor.mk

# -------------------------------------------------------------------------------------------------------------------- #
# -------------------------------------------------------------------------------------------------------------------- #
Expand Down Expand Up @@ -104,6 +105,14 @@ help:
@printf "$(BOLD)$(BLUE)Caddy Commands:$(NC)\n"
@printf " $(BOLD)$(GREEN)caddy-gen-cert$(NC) : Generate the caddy's mtls certificates.\n"
@printf " $(BOLD)$(GREEN)caddy-del-cert$(NC) : Remove the caddy's mtls certificates.\n"
@printf " $(BOLD)$(GREEN)caddy-validate$(NC) : Validates caddy's files syntax.\n"
@printf " $(BOLD)$(GREEN)caddy-validate$(NC) : Validates caddy's files syntax.\n\n"

@printf "$(BOLD)$(BLUE)Monitoring Commands:$(NC)\n"
@printf " $(BOLD)$(GREEN)monitor-up$(NC) : Start the monitoring stack (Prometheus, Grafana).\n"
@printf " $(BOLD)$(GREEN)monitor-down$(NC) : Stop the monitoring stack.\n"
@printf " $(BOLD)$(GREEN)monitor-status$(NC) : Show status of monitoring services.\n"
@printf " $(BOLD)$(GREEN)monitor-test$(NC) : Run monitoring stack test suite.\n"
@printf " $(BOLD)$(GREEN)monitor-grafana$(NC) : Open Grafana dashboards in browser.\n"
@printf " $(BOLD)$(GREEN)monitor-help$(NC) : Show detailed monitoring commands.\n"

@printf "$(NC)\n"
269 changes: 260 additions & 9 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ volumes:
caddy_config:
go_mod_cache:
driver: local
prometheus_data_prod:
driver: local
prometheus_data_local:
driver: local
grafana_data_prod:
driver: local
grafana_data_local:
driver: local

# --- DB: Define a named volume at the top level.
# Docker will manage its lifecycle.
Expand All @@ -30,7 +38,7 @@ services:
caddy_prod:
image: api-caddy_prod
build:
context: ./caddy
context: ./infra/caddy
dockerfile: Dockerfile
args:
- CADDY_VERSION=2.10.2
Expand All @@ -40,24 +48,35 @@ services:
restart: unless-stopped
depends_on:
- api

# --- The 443:443/udp is required for HTTP/3
# NOTES:
# - Admin API (2019) listens on all interfaces but is NOT published to host
# - Prometheus scrapes metrics from dedicated endpoint (9180) via Docker internal DNS
ports:
- "80:80"
- "443:443"
- "443:443/udp" # Required for HTTP/3
# NOTE: Admin API (2019) is NOT published to host (internal Docker network only)
# Prometheus scrapes Caddy metrics from :9180 via Docker internal DNS

# --- Dedicated /metrics endpoint for Prometheus (internal network only)
expose:
- "9180"
volumes:
- caddy_data:/data
- caddy_config:/config
- ./caddy/Caddyfile.prod:/etc/caddy/Caddyfile
- ./infra/caddy/Caddyfile.prod:/etc/caddy/Caddyfile
- ${CADDY_LOGS_PATH}:/var/log/caddy
- ./caddy/mtls:/etc/caddy/mtls:ro
- ./infra/caddy/mtls:/etc/caddy/mtls:ro
networks:
caddy_net:
aliases:
- proxy

caddy_local:
build:
context: ./caddy
context: ./infra/caddy
dockerfile: Dockerfile
args:
- CADDY_VERSION=2.10.2
Expand All @@ -68,15 +87,247 @@ services:
depends_on:
- api
ports:
- "8080:80"
- "18080:80"
- "8443:443"
- "127.0.0.1:2019:2019" # Admin API - localhost only for debugging

# --- Dedicated /metrics endpoint for Prometheus (internal network only)
expose:
- "9180"

volumes:
- caddy_data:/data
- caddy_config:/config
- ./caddy/mtls:/etc/caddy/mtls:ro
- ./caddy/Caddyfile.local:/etc/caddy/Caddyfile
- ./infra/caddy/mtls:/etc/caddy/mtls:ro
- ./infra/caddy/Caddyfile.local:/etc/caddy/Caddyfile
networks:
- caddy_net

prometheus:
image: prom/prometheus:v3.0.1
profiles: ["prod"]
container_name: oullin_prometheus
restart: unless-stopped
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
ports:
- "127.0.0.1:9090:9090"
volumes:
- ./infra/metrics/prometheus/provisioning/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data_prod:/prometheus
networks:
- caddy_net
- oullin_net
depends_on:
caddy_prod:
condition: service_started
postgres_exporter:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9090/-/healthy"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.25'
memory: 256M

prometheus_local:
image: prom/prometheus:v3.0.1
profiles: ["local"]
container_name: oullin_prometheus_local
restart: unless-stopped
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=7d'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
ports:
- "9090:9090"
volumes:
- ./infra/metrics/prometheus/provisioning/prometheus.local.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data_local:/prometheus
networks:
- caddy_net
- oullin_net
depends_on:
caddy_local:
condition: service_started
postgres_exporter_local:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9090/-/healthy"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.25'
memory: 256M

postgres_exporter:
image: prometheuscommunity/postgres-exporter:v0.15.0
profiles: ["prod"]
container_name: oullin_postgres_exporter
restart: unless-stopped
entrypoint: ["/postgres-exporter-entrypoint.sh"]
volumes:
- ./infra/metrics/prometheus/scripts/postgres-exporter-entrypoint.sh:/postgres-exporter-entrypoint.sh:ro
secrets:
- pg_username
- pg_password
- pg_dbname
networks:
- oullin_net
- caddy_net
depends_on:
api-db:
condition: service_healthy
expose:
- "9187"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9187/"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
deploy:
resources:
limits:
cpus: '0.25'
memory: 128M
reservations:
cpus: '0.05'
memory: 32M

postgres_exporter_local:
image: prometheuscommunity/postgres-exporter:v0.15.0
profiles: ["local"]
container_name: oullin_postgres_exporter_local
restart: unless-stopped
entrypoint: ["/postgres-exporter-entrypoint.sh"]
volumes:
- ./infra/metrics/prometheus/scripts/postgres-exporter-entrypoint.sh:/postgres-exporter-entrypoint.sh:ro
secrets:
- pg_username
- pg_password
- pg_dbname
networks:
- oullin_net
- caddy_net
depends_on:
api-db:
condition: service_healthy
expose:
- "9187"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9187/"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
deploy:
resources:
limits:
cpus: '0.25'
memory: 128M
reservations:
cpus: '0.05'
memory: 32M

grafana:
image: grafana/grafana:11.4.0
profiles: ["prod"]
container_name: oullin_grafana
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
environment:
- GF_SERVER_ROOT_URL=http://localhost:3000
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:?GRAFANA_ADMIN_PASSWORD must be set in .env file}
- GF_USERS_ALLOW_SIGN_UP=false
- GF_AUTH_ANONYMOUS_ENABLED=false
- GF_INSTALL_PLUGINS=
- GF_DATASOURCE_PROMETHEUS_URL=http://oullin_prometheus:9090
volumes:
- grafana_data_prod:/var/lib/grafana
- ./infra/metrics/grafana/provisioning:/etc/grafana/provisioning:ro
- ./infra/metrics/grafana/dashboards:/var/lib/grafana/dashboards:ro
networks:
- caddy_net
depends_on:
prometheus:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.1'
memory: 128M

grafana_local:
image: grafana/grafana:11.4.0
profiles: ["local"]
container_name: oullin_grafana_local
restart: unless-stopped
ports:
- "3000:3000"
environment:
- GF_SERVER_ROOT_URL=http://localhost:3000
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:?GRAFANA_ADMIN_PASSWORD must be set in .env file}
- GF_USERS_ALLOW_SIGN_UP=false
- GF_AUTH_ANONYMOUS_ENABLED=false
- GF_INSTALL_PLUGINS=
- GF_DATASOURCE_PROMETHEUS_URL=http://oullin_prometheus_local:9090
volumes:
- grafana_data_local:/var/lib/grafana
- ./infra/metrics/grafana/provisioning:/etc/grafana/provisioning:ro
- ./infra/metrics/grafana/dashboards:/var/lib/grafana/dashboards:ro
networks:
- caddy_net
depends_on:
prometheus_local:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.1'
memory: 128M

# A dedicated service for running one-off Go commands
api-runner:
Expand All @@ -86,7 +337,7 @@ services:
- ./.env
build:
context: .
dockerfile: ./docker/dockerfile-api
dockerfile: ./infra/docker/dockerfile-api
target: builder
volumes:
- .:/app
Expand Down Expand Up @@ -128,7 +379,7 @@ services:
ENV_HTTP_HOST: 0.0.0.0
build:
context: .
dockerfile: ./docker/dockerfile-api
dockerfile: ./infra/docker/dockerfile-api
args:
- APP_VERSION=0.0.0.1
- APP_HOST_PORT=${ENV_HTTP_PORT}
Expand Down
Loading
Loading