feat(monitoring): replace Dozzle with full observability stack

Replace the single-purpose Dozzle log viewer with a comprehensive monitoring
stack covering metrics, container telemetry, and persistent log aggregation.

Stack changes (docker-stack-service.yml -> docker-stack-monitoring.yml):
- remove Dozzle service and dozzle_users Docker secret
- add Portainer CE + portainer-agent (Swarm management UI)
- add node-exporter (global) — host CPU, memory, disk, network metrics
- add cAdvisor (global) — per-container resource usage metrics
- add Loki (replicated, service node) — persistent log storage, 31-day retention
- add Promtail (global) — Docker service discovery; ships logs with service,
  stack, container, and project labels; sends to Loki
- rename stack to iklimco-monitoring; add loki-vl persistent volume

Workflow (.gitea/workflows/deploy-prod.yml -> deploy-monitoring-prod.yml):
- rename file and add paths filter (Environment_Monitoring/**)
- remove Dozzle secret creation and auth handling
- add IMAGE_LOKI / IMAGE_PROMTAIL; clean up legacy dozzle_users Docker secret
- update SWAG step to loop swag/site-confs/*.conf.tpl (portainer only)
- remove DOZZLE_SUBDOMAIN; remove dozzle DNS record; keep portainer DNS
- replace "Wait for Dozzle" with "Wait for Loki"

SWAG:
- remove swag/dozzle.conf.tpl (Dozzle no longer in stack)
- add swag/site-confs/portainer.conf.tpl (moved from main repo template dir;
  monitoring stack manages its own SWAG configs independently)
- remove init/apisix-dozzle.sh (superseded by SWAG reverse proxy)

README:
- rewrite in Turkish; document Portainer, node-exporter, cAdvisor, Loki, Promtail
- add Grafana log viewing guide: datasource setup, label filter table, LogQL
  examples, metric-log correlation workflow, adding log panels to dashboards

Requires IMAGE_LOKI and IMAGE_PROMTAIL to be defined in .env and
corresponding custom images (build/loki/, build/promtail/) pushed to Harbor.
This commit is contained in:
Murat ÖZDEMİR 2026-06-24 21:21:02 +03:00
parent 94dc1d2fe3
commit 735d957dfa
6 changed files with 298 additions and 188 deletions

View File

@ -4,6 +4,8 @@ on:
push: push:
branches: branches:
- prod-env - prod-env
paths:
- 'Environment_Monitoring/**'
concurrency: concurrency:
group: prod-monitoring-deploy group: prod-monitoring-deploy
@ -47,104 +49,81 @@ jobs:
test -s .env test -s .env
test -s .env.secrets.swag test -s .env.secrets.swag
- name: Create Dozzle Auth Secret - name: Deploy Monitoring Stack
env:
DOZZLE_USERS_YML_B64: ${{ secrets.DOZZLE_USERS_YML_B64 }}
DOZZLE_USERS_YML: ${{ secrets.DOZZLE_USERS_YML }}
run: |
set -euo pipefail
if [ -n "${DOZZLE_USERS_YML_B64:-}" ]; then
printf '%s' "$DOZZLE_USERS_YML_B64" | base64 -d > /tmp/dozzle-users.yml
elif [ -n "${DOZZLE_USERS_YML:-}" ]; then
printf '%s\n' "$DOZZLE_USERS_YML" > /tmp/dozzle-users.yml
else
echo "DOZZLE_USERS_YML_B64 or DOZZLE_USERS_YML secret is required"
exit 1
fi
grep -q '^users:' /tmp/dozzle-users.yml
docker service rm iklimco_dozzle || true
for i in $(seq 1 24); do
if ! docker service inspect iklimco_dozzle >/dev/null 2>&1; then
break
fi
echo "Waiting for old iklimco_dozzle service to be removed..."
sleep 5
done
docker secret rm dozzle_users || true
docker secret create dozzle_users /tmp/dozzle-users.yml >/dev/null
shred -u /tmp/dozzle-users.yml || rm -f /tmp/dozzle-users.yml
- name: Deploy Dozzle Stack
run: | run: |
set -a; . ./.env; set +a set -a; . ./.env; set +a
export IMAGE_DOZZLE="${IMAGE_DOZZLE:-amir20/dozzle:v10.6.6}" export IMAGE_LOKI="${IMAGE_LOKI}"
export DOZZLE_USERS_SECRET_NAME=dozzle_users export IMAGE_PROMTAIL="${IMAGE_PROMTAIL}"
# Remove leftover dozzle_users Docker secret from previous setup
docker secret rm dozzle_users 2>/dev/null || true
docker stack deploy \ docker stack deploy \
--with-registry-auth \
--resolve-image changed \ --resolve-image changed \
-c docker-stack-service.yml \ -c Environment_Monitoring/docker-stack-monitoring.yml \
iklimco iklimco-monitoring
- name: Wait for Dozzle - name: Wait for Loki
run: | run: |
for i in $(seq 1 36); do for i in $(seq 1 36); do
REPLICAS=$(docker service ls --filter name=iklimco_dozzle --format "{{.Replicas}}" | head -1) REPLICAS=$(docker service ls --filter name=iklimco-monitoring_loki --format "{{.Replicas}}" | head -1)
if echo "$REPLICAS" | awk -F'[/ ]' '$1>0 && $1==$2{found=1} END{exit !found}'; then if echo "$REPLICAS" | awk -F'[/ ]' '$1>0 && $1==$2{found=1} END{exit !found}'; then
echo "Dozzle is ready: $REPLICAS" echo "Loki is ready: $REPLICAS"
exit 0 exit 0
fi fi
echo "Dozzle not ready yet (${REPLICAS:-missing}), waiting 5s..." echo "Loki not ready yet (${REPLICAS:-missing}), waiting 5s..."
sleep 5 sleep 5
done done
docker service ps iklimco_dozzle || true docker service ps iklimco-monitoring_loki || true
exit 1 exit 1
- name: Configure SWAG Reverse Proxy - name: Configure SWAG Reverse Proxy
run: | run: |
set -a; . ./.env; . ./.env.secrets.swag; set +a set -a; . ./.env; . ./.env.secrets.swag; set +a
export DOZZLE_SUBDOMAIN="${DOZZLE_SUBDOMAIN:-dozzle.iklim.co}" export PORTAINER_SUBDOMAIN="${PORTAINER_SUBDOMAIN:-portainer.iklim.co}"
envsubst '${DOZZLE_SUBDOMAIN}' < swag/dozzle.conf.tpl | docker run --rm -i \ export RESTRICTED_IPS_BLOCK="$(echo "$RESTRICTED_IPS" | tr ',' '\n' | sed 's|.*| allow &;|')"
-v "${SWAG_SITE_CONFS_DIR}:/output" \
alpine sh -c "cat > /output/dozzle.conf" SWAG_VARS='${PORTAINER_SUBDOMAIN}${RESTRICTED_IPS_BLOCK}'
for tpl in Environment_Monitoring/swag/site-confs/*.conf.tpl; do
fname=$(basename "${tpl%.tpl}")
envsubst "$SWAG_VARS" < "$tpl" | docker run --rm -i \
-v "${SWAG_SITE_CONFS_DIR}:/output" \
alpine sh -c "cat > /output/${fname}"
echo "${fname} written"
done
SWAG_CTR=$(docker ps -q -f name=iklimco_swag 2>/dev/null | head -1) SWAG_CTR=$(docker ps -q -f name=iklimco_swag 2>/dev/null | head -1)
if [ -n "$SWAG_CTR" ]; then if [ -n "$SWAG_CTR" ]; then
docker exec "$SWAG_CTR" nginx -t && docker exec "$SWAG_CTR" nginx -s reload docker exec "$SWAG_CTR" nginx -t && docker exec "$SWAG_CTR" nginx -s reload
fi fi
- name: Configure APISIX Reverse Proxy - name: Update DNS Records
run: |
set -a; . ./.env; set +a
export SPRING_PROFILES_ACTIVE=prod
export DOZZLE_SUBDOMAIN="${DOZZLE_SUBDOMAIN:-dozzle.iklim.co}"
/bin/bash init/apisix-dozzle.sh
- name: Update DNS Record
run: | run: |
set -a; . ./.env; . ./.env.secrets.swag; set +a set -a; . ./.env; . ./.env.secrets.swag; set +a
FLOATING_IP="${{ vars.PROD_FLOATING_IP }}" FLOATING_IP="${{ vars.PROD_FLOATING_IP }}"
DOMAIN="iklim.co" DOMAIN="iklim.co"
RECORD="${DOZZLE_DNS_RECORD:-dozzle}"
CURRENT=$(curl -s \ for record in portainer; do
-H "Authorization: sso-key ${GODADDY_KEY}:${GODADDY_SECRET}" \ CURRENT=$(curl -s \
"https://api.godaddy.com/v1/domains/${DOMAIN}/records/A/${RECORD}" \
2>/dev/null | jq -r '.[0].data // empty' 2>/dev/null || true)
if [ "$CURRENT" = "$FLOATING_IP" ]; then
echo "${RECORD}.${DOMAIN} -> ${FLOATING_IP} exists, skipping"
else
curl -sf -X PUT \
-H "Authorization: sso-key ${GODADDY_KEY}:${GODADDY_SECRET}" \ -H "Authorization: sso-key ${GODADDY_KEY}:${GODADDY_SECRET}" \
-H "Content-Type: application/json" \ "https://api.godaddy.com/v1/domains/${DOMAIN}/records/A/${record}" \
"https://api.godaddy.com/v1/domains/${DOMAIN}/records/A/${RECORD}" \ 2>/dev/null | jq -r '.[0].data // empty' 2>/dev/null || true)
-d "[{\"data\":\"${FLOATING_IP}\",\"ttl\":600}]"
echo "${RECORD}.${DOMAIN} -> ${FLOATING_IP} added/updated" if [ "$CURRENT" = "$FLOATING_IP" ]; then
fi echo "${record}.${DOMAIN} -> ${FLOATING_IP} exists, skipping"
else
curl -sf -X PUT \
-H "Authorization: sso-key ${GODADDY_KEY}:${GODADDY_SECRET}" \
-H "Content-Type: application/json" \
"https://api.godaddy.com/v1/domains/${DOMAIN}/records/A/${record}" \
-d "[{\"data\":\"${FLOATING_IP}\",\"ttl\":600}]"
echo "${record}.${DOMAIN} -> ${FLOATING_IP} added/updated"
fi
done
- name: Verify Deployment - name: Verify Deployment
run: | run: |
docker service ps iklimco_dozzle \ docker service ps iklimco-monitoring_loki \
--filter "desired-state=running" \ --filter "desired-state=running" \
--format "table {{.Name}}\t{{.Node}}\t{{.CurrentState}}\t{{.Image}}" | head -20 --format "table {{.Name}}\t{{.Node}}\t{{.CurrentState}}\t{{.Image}}" | head -20

151
README.md
View File

@ -1,36 +1,143 @@
# Environment Monitoring # Environment Monitoring
Dozzle is deployed as a separate Swarm monitoring service. Tüm izleme servisleri `docker-stack-monitoring.yml` stack'inde yönetilir: Portainer, node-exporter, cAdvisor, Loki ve Promtail.
## Production ## Servisler
- Swarm mode is enabled with `DOZZLE_MODE=swarm`. ### Portainer
- The service uses `deploy.mode: global` so one Dozzle task runs on every Swarm node.
- Dozzle is attached to both `dozzle` and `iklimco-net`.
- Docker socket is mounted read-only.
- Simple authentication is enabled with `DOZZLE_AUTH_PROVIDER=simple`.
- The real `users.yml` must be supplied through Gitea secret `DOZZLE_USERS_YML_B64` or `DOZZLE_USERS_YML`.
- Prefer `roles: none` for read-only log viewing. Dozzle defaults to full action access when roles are omitted.
Generate a bcrypt-backed users file with Dozzle itself: Docker Swarm yönetim arayüzü.
- `portainer-agent` — global mode, tüm node'larda çalışır; Docker socket ve volume bilgisini Portainer CE'ye aktarır
- `portainer` — tek replica, manager node'da; `portainer-net` overlay üzerinden agent'lara bağlanır
- Dış erişim SWAG üzerinden: `portainer.iklim.co`
### node-exporter
Host / işletim sistemi metriklerini Prometheus'a aktarır. Her node'da çalışır (`deploy.mode: global`).
Toplanan metrikler: CPU kullanımı, bellek, disk I/O, ağ trafiği, sistem yükü, dosya sistemi doluluk oranları.
### cAdvisor
Container ve Swarm servis bazlı kaynak tüketimini Prometheus'a aktarır. Her node'da çalışır (`deploy.mode: global`).
Toplanan metrikler: container başına CPU/bellek/ağ/disk I/O.
### Loki
Container loglarını toplayan ve saklayan log aggregation servisi. Grafana'nın native Loki datasource entegrasyonu ile metriklerle birlikte sorgulanabilir.
- Tek replica, `node.labels.type == service` node'unda çalışır
- Log saklama süresi: 31 gün (`limits_config.retention_period`)
- Konfigürasyon: `build/loki/loki.yml`
### Promtail
Her node'daki container loglarını Docker API üzerinden toplayarak Loki'ye gönderir. Her node'da çalışır (`deploy.mode: global`).
- Docker service discovery: container adı, servis adı, stack adı ve proje label'larını otomatik etiket olarak ekler
- Konfigürasyon: `build/promtail/promtail.yml`
### Health Agent
Cluster içi servis sağlığını Uptime Kuma'ya push eden Python servisi. Ayrıntılar için `health-agent/README.md`.
---
## Grafana'da Log Görüntüleme
### İlk Kurulum — Loki Datasource Ekleme
Loki deploy edildikten sonra tek seferlik yapılır:
1. Grafana UI → Sol menü → **Connections → Data sources**
2. **Add new data source****Loki** seç
3. URL: `http://loki:3100`
4. **Save & test** — "Data source connected" mesajı görünmeli
Alternatif olarak `template/grafana/provisioning/datasources/loki.yaml` dosyasını Grafana'nın provisioning dizinine kopyalayarak otomatik yüklenebilir:
```bash ```bash
docker run -it --rm amir20/dozzle:v10.6.6 generate admin \ # Grafana veri dizinine provisioning dosyasını kopyala
--password '<strong-password>' \ cp template/grafana/provisioning/datasources/loki.yaml \
--email admin@iklim.co \ <GRAFANA_DATA_DIR>/provisioning/datasources/loki.yaml
--name 'Admin' \ # Grafana'yı yeniden başlat
--user-roles none > users.yml docker service update --force iklimco_grafana
``` ```
Store the result as either: ### Log Görüntüleme — Explore
- `DOZZLE_USERS_YML_B64`: `base64 -w0 users.yml` 1. Sol menü → **Explore** (pusula ikonu)
- `DOZZLE_USERS_YML`: raw multiline file content 2. Üst sol köşeden datasource olarak **Loki** seç
3. **Label filters** ile filtrele:
Default public hostname: | Etiket | Açıklama | Örnek |
|--------|----------|-------|
| `service` | Swarm servis adı | `iklimco_vault` |
| `stack` | Stack adı | `iklimco` |
| `container` | Container adı | `iklimco_vault.1.xxx` |
| `project` | project label değeri | `co.iklim` |
| `logstream` | stdout / stderr | `stderr` |
```text ### LogQL Örnekleri
dozzle.iklim.co
```logql
# Belirli bir servisin tüm logları
{service="iklimco_vault"}
# Tüm stack'teki hata logları
{stack="iklimco"} |= "ERROR"
# Exception içeren loglar (tüm servisler)
{project="co.iklim"} |= "Exception"
# APISIX erişim logları — sadece 5xx'ler
{service="iklimco_apisix"} | json | status >= 500
# Belirli zaman aralığında servis logları (UI'dan zaman seçimi de yapılabilir)
{service="iklimco_rabbitmq"} |= "error" | line_format "{{.line}}"
``` ```
The workflow writes SWAG reverse proxy config, configures APISIX, updates DNS, deploys the Swarm stack, and verifies `iklimco_dozzle`. ### Metrik — Log Korelasyonu
Grafana'nın en güçlü özelliklerinden biri: bir Prometheus metriğinde anomali gördüğünde aynı zaman aralığındaki logları yan yana inceleyebilirsin.
1. Prometheus dashboard panelinde anomali noktasına tıkla
2. **Explore** linkine tıkla → aynı zaman aralığı Explore'da açılır
3. Datasource'u Loki'ye geçir, ilgili servisi filtrele
4. Metrik spike'ı ile log hataları aynı anda görünür
### Dashboard'a Log Paneli Ekleme
Mevcut dashboard'lara log paneli eklemek için:
1. Dashboard → **Edit** → **Add visualization**
2. Panel tipi: **Logs** seç
3. Datasource: **Loki**
4. Query: `{service="<servis_adı>"}`
5. **Deduplication** seçeneğini açabilirsin (tekrar eden satırları gizler)
---
## Ağ Yapısı
| Network | Tür | Kullananlar |
|---------|-----|-------------|
| `iklimco-net` | external overlay | Portainer, node-exporter, cAdvisor, Loki, Promtail, Health Agent |
| `portainer-net` | stack overlay | portainer-agent ↔ portainer iletişimi |
---
## Deployment
```bash
docker stack deploy \
--with-registry-auth \
-c Environment_Monitoring/docker-stack-monitoring.yml \
iklimco-monitoring
```
Prod için Gitea workflow'u: `Environment_Monitoring/.gitea/workflows/deploy-monitoring-prod.yml`
> **Not:** Loki ve Promtail custom image kullanır (`build/loki/`, `build/promtail/`). Deploy öncesinde imajların Harbor'a build edilip push edilmesi gerekir. `.env` dosyasında `IMAGE_LOKI` ve `IMAGE_PROMTAIL` değişkenlerinin tanımlı olması zorunludur.

118
docker-stack-monitoring.yml Normal file
View File

@ -0,0 +1,118 @@
services:
portainer-agent:
image: portainer/agent:lts
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/docker/volumes:/var/lib/docker/volumes
networks:
- portainer-net
deploy:
mode: global
placement:
constraints:
- node.platform.os == linux
restart_policy:
condition: any
delay: 5s
labels:
project: co.iklim
portainer:
image: portainer/portainer-ce:lts
command: -H tcp://tasks.portainer-agent:9001 --tlsskipverify
volumes:
- portainer_data:/data
networks:
- portainer-net
- iklimco-net
deploy:
mode: replicated
replicas: 1
placement:
constraints:
- node.role == manager
restart_policy:
condition: any
delay: 5s
labels:
project: co.iklim
node-exporter:
image: prom/node-exporter:latest
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--path.rootfs=/rootfs'
volumes:
- /:/rootfs:ro
- /proc:/host/proc:ro
- /sys:/host/sys:ro
networks:
- iklimco-net
deploy:
mode: global
restart_policy:
condition: any
delay: 5s
labels:
project: co.iklim
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker:/var/lib/docker:ro
networks:
- iklimco-net
deploy:
mode: global
restart_policy:
condition: any
delay: 5s
labels:
project: co.iklim
loki:
image: ${CUSTOM_IMAGE_REGISTRY}${IMAGE_LOKI}
volumes:
- loki-vl:/loki
networks:
- iklimco-net
deploy:
mode: replicated
replicas: 1
placement:
constraints:
- node.labels.type == service
restart_policy:
condition: any
delay: 5s
labels:
project: co.iklim
promtail:
image: ${CUSTOM_IMAGE_REGISTRY}${IMAGE_PROMTAIL}
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- iklimco-net
deploy:
mode: global
restart_policy:
condition: any
delay: 5s
labels:
project: co.iklim
networks:
portainer-net:
driver: overlay
attachable: true
iklimco-net:
external: true
volumes:
portainer_data:
loki-vl:

View File

@ -1,38 +0,0 @@
services:
dozzle:
image: ${IMAGE_DOZZLE:-amir20/dozzle:v10.6.6}
environment:
- DOZZLE_MODE=swarm
- DOZZLE_AUTH_PROVIDER=simple
- DOZZLE_NO_ANALYTICS=true
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
secrets:
- source: dozzle_users
target: /data/users.yml
mode: 0400
networks:
- dozzle
- iklimco-net
deploy:
mode: global
restart_policy:
condition: any
delay: 5s
update_config:
parallelism: 1
order: start-first
labels:
project: co.iklim
secrets:
dozzle_users:
external: true
name: ${DOZZLE_USERS_SECRET_NAME:-dozzle_users}
networks:
dozzle:
driver: overlay
attachable: true
iklimco-net:
external: true

View File

@ -1,59 +0,0 @@
#!/bin/bash
set -euo pipefail
PROFILE=${SPRING_PROFILES_ACTIVE:-prod}
if [[ "$PROFILE" == "dev" ]]; then
APISIX_ADMIN_URL=http://${LAN_IP:-127.0.0.1}:9180/apisix/admin
else
APISIX_ADMIN_URL=http://apisix:9180/apisix/admin
fi
API_KEY=${APISIX_ADMIN_KEY:?APISIX_ADMIN_KEY is required}
DOZZLE_HOST=${DOZZLE_SUBDOMAIN:-dozzle.iklim.co}
DOZZLE_NODE=${DOZZLE_NODE:-dozzle:8080}
ERRORS=0
call_api() {
local label="$1"; shift
local http_code
http_code=$(curl -sS -o /tmp/apisix_dozzle_resp.json -w "%{http_code}" "$@")
if [[ "$http_code" -ge 400 ]]; then
echo "ERROR: $label (HTTP $http_code)"
cat /tmp/apisix_dozzle_resp.json
echo
ERRORS=$((ERRORS + 1))
fi
}
until curl -sf -o /dev/null -H "X-API-KEY: $API_KEY" "$APISIX_ADMIN_URL/upstreams"; do
echo "APISIX not ready, retrying in 3s..."
sleep 3
done
HC='"checks":{"active":{"type":"http","http_path":"/","timeout":5,"healthy":{"interval":10,"successes":1},"unhealthy":{"interval":5,"http_failures":3}},"passive":{"healthy":{"http_statuses":[200,201,204,302],"successes":2},"unhealthy":{"http_statuses":[429,500,502,503,504],"http_failures":3,"tcp_failures":3}}}'
if [[ "$PROFILE" != "dev" ]]; then
DOZZLE_ROUTE_PLUGINS=',"plugins":{"limit-count":{"count":120,"time_window":60,"key":"remote_addr","rejected_code":429,"policy":"local"}}'
else
DOZZLE_ROUTE_PLUGINS=""
fi
call_api "upstream dozzle" -X PUT "$APISIX_ADMIN_URL/upstreams/dozzle-upstream" \
-H "X-API-KEY: $API_KEY" -H "Content-Type: application/json" \
-d '{"name":"dozzle-upstream","type":"roundrobin","nodes":{"'"$DOZZLE_NODE"'":1},'"$HC"'}'
call_api "service dozzle" -X PUT "$APISIX_ADMIN_URL/services/dozzle-service" \
-H "X-API-KEY: $API_KEY" -H "Content-Type: application/json" \
-d '{"name":"dozzle-service","upstream_id":"dozzle-upstream","enable_websocket":true}'
call_api "route dozzle" -X PUT "$APISIX_ADMIN_URL/routes/dozzle-route" \
-H "X-API-KEY: $API_KEY" -H "Content-Type: application/json" \
-d '{"name":"dozzle-route","hosts":["'"$DOZZLE_HOST"'"],"uri":"/*","methods":["GET","POST","PUT","DELETE","PATCH","OPTIONS"],"service_id":"dozzle-service","enable_websocket":true'"$DOZZLE_ROUTE_PLUGINS"'}'
if [ "$ERRORS" -gt 0 ]; then
echo "Dozzle APISIX init completed with $ERRORS error(s)."
exit 1
fi
echo "Dozzle APISIX route configured for https://${DOZZLE_HOST}"

View File

@ -2,7 +2,7 @@ server {
listen 443 ssl; listen 443 ssl;
listen [::]:443 ssl; listen [::]:443 ssl;
http2 on; http2 on;
server_name ${DOZZLE_SUBDOMAIN}; server_name ${PORTAINER_SUBDOMAIN};
include /config/nginx/ssl.conf; include /config/nginx/ssl.conf;
include /config/nginx/resolver.conf; include /config/nginx/resolver.conf;
@ -10,10 +10,13 @@ server {
client_max_body_size 0; client_max_body_size 0;
location / { location / {
${RESTRICTED_IPS_BLOCK}
deny all;
include /config/nginx/proxy.conf; include /config/nginx/proxy.conf;
include /config/nginx/resolver.conf; include /config/nginx/resolver.conf;
set $upstream_app apisix; set $upstream_app portainer;
set $upstream_port 9080; set $upstream_port 9000;
set $upstream_proto http; set $upstream_proto http;
proxy_pass $upstream_proto://$upstream_app:$upstream_port; proxy_pass $upstream_proto://$upstream_app:$upstream_port;
} }