Overview #
This deployment tutorial primarily focuses on setting up a Kubernetes-based Loki & Grafana stack, featuring multiple Loki instances that serve as separate data sources for Grafana.
For a wider range of Promtail scrapping configurations check out my first post in this series: Loki & Grafana - Docker Compose Stack
In this tutorial I’m using a Kubernetes K8s cluster with MetalLB deployed with Kubespray on Debian 12 servers:
192.168.30.71 deb-02 # Controller / Master Node
192.168.30.72 deb-03 # Controller / Master Node
192.168.30.73 deb-04 # Worker Node
192.168.30.74 deb-05 # Worker Node
192.168.30.60 # NFS Server, Promtail Container for bare-metal systemd-journal scrapping
192.168.30.61 # Promtail Container for Docker container scrapping
NFS Prerequisites #
The following steps are only necessary for the Loki deployment variant with a NFS volume for peristent data.
NFS Server Setup #
Loki Folder Structure #
Create the folder structure for the Loki volume mounts:
# Create folder structure for the Loki volume mapping
sudo mkdir -p /srv/nfs/k8s_share/{loki-1,loki-2,loki-3}
# Change the owner
sudo chown 10001:10001 /srv/nfs/k8s_share/{loki-1,loki-2,loki-3}
NFS Exports #
I’m using the following NFS server configuration:
# Install NFS package
sudo apt install nfs-kernel-server
# Open NFS configuration
sudo vi /etc/exports
# NFS configuration: Define the kubernetes nodes
/srv/nfs/k8s_share 192.168.30.71(rw,sync,no_root_squash)
/srv/nfs/k8s_share 192.168.30.72(rw,sync,no_root_squash)
/srv/nfs/k8s_share 192.168.30.73(rw,sync,no_root_squash)
/srv/nfs/k8s_share 192.168.30.74(rw,sync,no_root_squash)
# Restart NFS server
sudo systemctl restart nfs-server
Install NFS on Kubernetes Nodes #
Install the NFS utilities package on all Kubernetes nodes:
# Install NFS utilities package
sudo apt install nfs-common -y
Optional: Verify the NFS connectivity
# Verify that the NFS server is correctly configured
/usr/sbin/showmount -e 192.168.30.60
# Shell output:
Export list for 192.168.30.60:
/srv/nfs/k8s_share 192.168.30.74,192.168.30.73,192.168.30.72,192.168.30.71
Loki Grafana Stack #
Create Namespace #
# Create a namespace for the Loki & Grafana deployments
kubectl create namespace loki-grafana-stack
Grafana Deployment #
Deployment & NodePort Service #
# Create a configuration for the Grafana Deployment
vi grafana-deployment.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: grafana-datasources
data:
  datasources.yaml: |
    apiVersion: 1
    datasources:
      - name: Loki-K8s-Cluser
        type: loki
        access: proxy
        url: http://loki-1:3101
        version: 1
        editable: false
        isDefault: true
      - name: Loki-VarLog
        type: loki
        access: proxy
        url: http://loki-2:3102
        version: 1
        editable: false
        isDefault: false
      - name: Loki-Container
        type: loki
        access: proxy
        url: http://loki-3:3103
        version: 1
        editable: false
        isDefault: false    
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grafana
spec:
  replicas: 1
  selector:
    matchLabels:
      app: grafana
  template:
    metadata:
      labels:
        app: grafana
    spec:
      containers:
      - name: grafana
        image: grafana/grafana:latest
        ports:
        - containerPort: 3000
        env:
        - name: GF_AUTH_ANONYMOUS_ENABLED
          value: "true"
        - name: GF_AUTH_ANONYMOUS_ORG_ROLE
          value: "Admin"
        - name: GF_AUTH_DISABLE_LOGIN_FORM
          value: "true"
        volumeMounts:
        - name: datasource-config
          mountPath: /etc/grafana/provisioning/datasources
          readOnly: true
      volumes:
      - name: datasource-config
        configMap:
          name: grafana-datasources
---
apiVersion: v1
kind: Service
metadata:
  name: grafana
spec:
  type: NodePort
  ports:
    - port: 3000
      targetPort: 3000
      nodePort: 30000
  selector:
    app: grafana
Kubernetes TLS Certificate Secret #
In this setup I’m using a Let’s Encrypt wildcard certificate.
# Create a Kubernetes secret for the TLS certificate
kubectl create secret tls grafana-tls --cert=./fullchain.pem --key=./privkey.pem -n loki-grafana-stack
Nginx Ingress for NodePort Service #
# Create YML file
vi grafana-ingress.yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: grafana
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
spec:
  tls:
  - hosts:
    - grafana.jklug.work
    secretName: grafana-tls
  rules:
  - host: grafana.jklug.work
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: grafana
            port:
              number: 3000
DNS Entry #
The DNS entry for the Nginx ingress must point to one of the worker nodes:
192.168.30.73 grafana.jklug.work
# Or 
192.168.30.74 grafana.jklug.work
Loki Instance 1 #
# Create a configuration for the frist Loki instance
vi loki-1-deployment.yaml
Deployment with NodePort Service
apiVersion: apps/v1
kind: Deployment
metadata:
  name: loki-1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: loki-1
  template:
    metadata:
      labels:
        app: loki-1
    spec:
      containers:
      - name: loki-1
        image: grafana/loki:latest
        ports:
        - containerPort: 3100
---
apiVersion: v1
kind: Service
metadata:
  name: loki-1
spec:
  type: NodePort
  ports:
    - port: 3101
      targetPort: 3100
      nodePort: 31001
  selector:
    app: loki-1
Deployment with LoadBalancer Service
apiVersion: apps/v1
kind: Deployment
metadata:
  name: loki-1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: loki-1
  template:
    metadata:
      labels:
        app: loki-1
    spec:
      containers:
      - name: loki-1
        image: grafana/loki:latest
        ports:
        - containerPort: 3100
---
apiVersion: v1
kind: Service
metadata:
  name: loki-1
spec:
  type: LoadBalancer
  ports:
    - port: 3101
      targetPort: 3100
  selector:
    app: loki-1
Deployment with LoadBalancer Service & NFS Volume
apiVersion: apps/v1
kind: Deployment
metadata:
  name: loki-1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: loki-1
  template:
    metadata:
      labels:
        app: loki-1
    spec:
      containers:
      - name: loki-1
        image: grafana/loki:latest
        ports:
        - containerPort: 3100
        volumeMounts:
          - name: nfs
            mountPath: "/loki"  # Loki data directory
      volumes:
        - name: nfs
          nfs:
            server: "192.168.30.60"
            path: "/srv/nfs/k8s_share/loki-1"
---
apiVersion: v1
kind: Service
metadata:
  name: loki-1
spec:
  type: LoadBalancer
  ports:
    - port: 3101
      targetPort: 3100
  selector:
    app: loki-1
Loki Instance 2 #
# Create a configuration for the second Loki instance
vi loki-2-deployment.yaml
Deployment with NodePort Service
apiVersion: apps/v1
kind: Deployment
metadata:
  name: loki-2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: loki-2
  template:
    metadata:
      labels:
        app: loki-2
    spec:
      containers:
      - name: loki
        image: grafana/loki:latest
        ports:
        - containerPort: 3100
---
apiVersion: v1
kind: Service
metadata:
  name: loki-2
spec:
  type: NodePort
  ports:
    - port: 3102
      targetPort: 3100
      nodePort: 31002
  selector:
    app: loki-2
Deployment with LoadBalancer Service
apiVersion: apps/v1
kind: Deployment
metadata:
  name: loki-2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: loki-2
  template:
    metadata:
      labels:
        app: loki-2
    spec:
      containers:
      - name: loki
        image: grafana/loki:latest
        ports:
        - containerPort: 3100
---
apiVersion: v1
kind: Service
metadata:
  name: loki-2
spec:
  type: LoadBalancer
  ports:
    - port: 3102
      targetPort: 3100
  selector:
    app: loki-2
Deployment with LoadBalancer Service & NFS Volume
apiVersion: apps/v1
kind: Deployment
metadata:
  name: loki-2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: loki-2
  template:
    metadata:
      labels:
        app: loki-2
    spec:
      containers:
      - name: loki
        image: grafana/loki:latest
        ports:
        - containerPort: 3100
        volumeMounts:
          - name: nfs
            mountPath: "/loki"  # Loki data directory
      volumes:
        - name: nfs
          nfs:
            server: "192.168.30.60"
            path: "/srv/nfs/k8s_share/loki-2"
---
apiVersion: v1
kind: Service
metadata:
  name: loki-2
spec:
  type: LoadBalancer
  ports:
    - port: 3102
      targetPort: 3100
  selector:
    app: loki-2
Loki Instance 3 #
# Create a configuration for the thrid Loki instance
vi loki-3-deployment.yaml
Deployment with NodePort Service
apiVersion: apps/v1
kind: Deployment
metadata:
  name: loki-3
spec:
  replicas: 1
  selector:
    matchLabels:
      app: loki-3
  template:
    metadata:
      labels:
        app: loki-3
    spec:
      containers:
      - name: loki
        image: grafana/loki:latest
        ports:
        - containerPort: 3100
---
apiVersion: v1
kind: Service
metadata:
  name: loki-3
spec:
  type: NodePort
  ports:
    - port: 3103
      targetPort: 3100
      nodePort: 31003
  selector:
    app: loki-3
Deployment with LoadBalancer Service
apiVersion: apps/v1
kind: Deployment
metadata:
  name: loki-3
spec:
  replicas: 1
  selector:
    matchLabels:
      app: loki-3
  template:
    metadata:
      labels:
        app: loki-3
    spec:
      containers:
      - name: loki
        image: grafana/loki:latest
        ports:
        - containerPort: 3100
---
apiVersion: v1
kind: Service
metadata:
  name: loki-3
spec:
  type: LoadBalancer
  ports:
    - port: 3103
      targetPort: 3100
  selector:
    app: loki-3
Deployment with LoadBalancer Service & NFS Volume
apiVersion: apps/v1
kind: Deployment
metadata:
  name: loki-3
spec:
  replicas: 1
  selector:
    matchLabels:
      app: loki-3
  template:
    metadata:
      labels:
        app: loki-3
    spec:
      containers:
      - name: loki
        image: grafana/loki:latest
        ports:
        - containerPort: 3100
        volumeMounts:
          - name: nfs
            mountPath: "/loki"  # Loki data directory
      volumes:
        - name: nfs
          nfs:
            server: "192.168.30.60"
            path: "/srv/nfs/k8s_share/loki-3"
---
apiVersion: v1
kind: Service
metadata:
  name: loki-3
spec:
  type: LoadBalancer
  ports:
    - port: 3103
      targetPort: 3100
  selector:
    app: loki-3
Deploy the Resources #
Deploy the resources:
# Deploy Grafana
kubectl apply -f grafana-deployment.yaml -n loki-grafana-stack
# Deploy Grafana Nginx Ingress
kubectl apply -f grafana-ingress.yml -n loki-grafana-stack
# Deploy Loki instance 1
kubectl apply -f loki-1-deployment.yaml -n loki-grafana-stack
# Deploy Loki instance 2
kubectl apply -f loki-2-deployment.yaml -n loki-grafana-stack
# Deploy Loki instance 3
kubectl apply -f loki-3-deployment.yaml -n loki-grafana-stack
Verify the Deployment #
Grafana Deployment, Loki Deployment with NodePort Service
# List all resources
kubectl get all -n loki-grafana-stack
# Shell output:
NAME                           READY   STATUS    RESTARTS   AGE
pod/grafana-7fddfb7557-fqp99   1/1     Running   0          56m
pod/loki-1-5cff777949-hcstd    1/1     Running   0          112s
pod/loki-2-59f556b498-9qs2b    1/1     Running   0          106s
pod/loki-3-769b7589fc-b297d    1/1     Running   0          102s
NAME              TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/grafana   NodePort   10.233.51.156   <none>        3000:30000/TCP   56m
service/loki-1    NodePort   10.233.1.30     <none>        3101:31001/TCP   112s
service/loki-2    NodePort   10.233.48.229   <none>        3102:31002/TCP   106s
service/loki-3    NodePort   10.233.49.146   <none>        3103:31003/TCP   6s
NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/grafana   1/1     1            1           56m
deployment.apps/loki-1    1/1     1            1           112s
deployment.apps/loki-2    1/1     1            1           106s
deployment.apps/loki-3    1/1     1            1           102s
NAME                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/grafana-7fddfb7557   1         1         1       56m
replicaset.apps/loki-1-5cff777949    1         1         1       112s
replicaset.apps/loki-2-59f556b498    1         1         1       106s
replicaset.apps/loki-3-769b7589fc    1         1         1       102s
Grafana Deployment, Loki Deployment with LoadBalancer Service
# List all resources
kubectl get all -n loki-grafana-stack
# Shell output:
NAME                           READY   STATUS    RESTARTS   AGE
pod/grafana-7fddfb7557-fqp99   1/1     Running   0          60m
pod/loki-1-5cff777949-xwmbf    1/1     Running   0          61s
pod/loki-2-59f556b498-fjh7l    1/1     Running   0          55s
pod/loki-3-769b7589fc-6mnt9    1/1     Running   0          49s
NAME              TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)          AGE
service/grafana   NodePort       10.233.51.156   <none>           3000:30000/TCP   60m
service/loki-1    LoadBalancer   10.233.49.223   192.168.30.241   3101:30896/TCP   61s
service/loki-2    LoadBalancer   10.233.56.37    192.168.30.242   3102:30187/TCP   55s
service/loki-3    LoadBalancer   10.233.59.215   192.168.30.243   3103:32203/TCP   49s
NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/grafana   1/1     1            1           60m
deployment.apps/loki-1    1/1     1            1           61s
deployment.apps/loki-2    1/1     1            1           55s
deployment.apps/loki-3    1/1     1            1           49s
NAME                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/grafana-7fddfb7557   1         1         1       60m
replicaset.apps/loki-1-5cff777949    1         1         1       61s
replicaset.apps/loki-2-59f556b498    1         1         1       55s
replicaset.apps/loki-3-769b7589fc    1         1         1       49s
Nginx Ingress & TLS Secret
# List the ingress resources
kubectl get ingress -n loki-grafana-stack
# Shell output:
NAME      CLASS    HOSTS                ADDRESS                       PORTS     AGE
grafana   <none>   grafana.jklug.work   192.168.30.73,192.168.30.74   80, 443   6m24s
# Verify the TLS secret
kubectl get secrets -n loki-grafana-stack
# Shell output:
NAME          TYPE                DATA   AGE
grafana-tls   kubernetes.io/tls   2      8s
Access Grafana #
# Access Grafana: NodePort Service
http://192.168.30.71:30000
http://192.168.30.72:30000
http://192.168.30.73:30000
http://192.168.30.74:30000
# Access Grafana: Via Nginx Ingress
https://grafana.jklug.work
Note: When accessing Grafana via the Nginx ingress, the DNS entry must point to one of the worker nodes http://192.168.30.73 or http://192.168.30.74.
Delete the Deployment #
# Delete the Grafana deployment
kubectl delete  -f grafana-deployment.yaml -n loki-grafana-stack
# Delete the Grafana Nginx ingress
kubectl delete ingress grafana  -n loki-grafana-stack
# Delete the TLS certificate secret
kubectl delete secret grafana-tls -n loki-grafana-stack
# Delete the Loki instance 1 deployment
kubectl delete  -f loki-1-deployment.yaml -n loki-grafana-stack
# Delete the Loki instance 2 deployment
kubectl delete  -f loki-2-deployment.yaml -n loki-grafana-stack
# Delete the Loki instance 3 deployment
kubectl delete  -f loki-3-deployment.yaml -n loki-grafana-stack
 
Promtail Helm Deployment for K8s Scrapping #
This Helm chart automatically deploys the Promtail pod on all Kubernetes controller & worker nodes.
The kubernetes logs can be filtered according to the following labels: app, component, container, filename, instance, job, namespace, node_name, pod, stream
Add Grafana Repository #
# Add Grafana repository
helm repo add grafana https://grafana.github.io/helm-charts
# Update package index
helm repo update
List Helm Charts #
# List the available Loki Charts in the Grafana Repository
helm search repo loki
# Shell output:
NAME                            CHART VERSION   APP VERSION     DESCRIPTION
grafana/loki                    6.6.2           3.0.0           Helm chart for Grafana Loki and Grafana Enterpr...
grafana/loki-canary             0.14.0          2.9.1           Helm chart for Grafana Loki Canary
grafana/loki-distributed        0.79.0          2.9.6           Helm chart for Grafana Loki in microservices mode
grafana/loki-simple-scalable    1.8.11          2.6.1           Helm chart for Grafana Loki in simple, scalable...
grafana/loki-stack              2.10.2          v2.9.3          Loki: like Prometheus, but for logs.
grafana/fluent-bit              2.6.0           v2.1.0          Uses fluent-bit Loki go plugin for gathering lo...
grafana/lgtm-distributed        1.0.1           6.59.4          Umbrella chart for a distributed Loki, Grafana,...
grafana/meta-monitoring         1.0.0           0.0.1           A Helm chart for meta monitoring Grafana Loki, ...
grafana/promtail                6.15.5          2.9.3           Promtail is an agent which ships the contents o..
Save Promtail Values #
# Save the Promtail deployment configuration
helm show values grafana/promtail > promtail-values.yaml
Adopt Promtail Values #
Adopt the Promtail clients URL to point to the Loki instance:
# Edit the Promtail deployment configuration
vi promtail-values.yaml
Loki with NodePort Service
# Define the IP and port of the Loki instance
  clients:
    - url: http://192.168.30.71:31001/loki/api/v1/push
Loki with LoadBalancer Service
# Define the IP and port of the Loki instance
  clients:
    - url: http://192.168.30.241:3101/loki/api/v1/push
Deploy Promtail #
# Deploy Promtail
helm install --values promtail-values.yaml promtail grafana/promtail
Shell Output
# Shell output:
NAME: promtail
LAST DEPLOYED: Mon Jun  3 18:10:48 2024
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
***********************************************************************
 Welcome to Grafana Promtail
 Chart version: 6.15.5
 Promtail version: 2.9.3
***********************************************************************
Verify the application is working by running these commands:
* kubectl --namespace default port-forward daemonset/promtail 3101
* curl http://127.0.0.1:3101/metrics
Delete Promtail Deployment #
# Delete the Promtail deployment
helm uninstall promtail
 
Promtail Container for Bare-Metal Scrapping #
Prerequisites #
Docker System User #
# Create a system user for Docker
sudo adduser --system --ingroup docker --shell /sbin/nologin docker-system
# Add the user to the systemd-journal & adm group
sudo usermod -aG systemd-journal docker-system
# Reboot the system to apply the group changes
sudo reboot
# Verify the user groups
groups docker-system
# Shell output
docker-system : docker systemd-journal
Promtail Container #
Folder Structure #
# Create folder struture
sudo mkdir -p /opt/promtail && cd /opt/promtail
Promtail Configuration #
# Create Promtail configuration file
sudo vi promtail.yaml
Promtail Config: Loki with NodePort Service
server:
  http_listen_port: 9080
  grpc_listen_port: 0
positions:
  filename: /tmp/positions.yaml
clients:
  - url: http://192.168.30.71:31002/loki/api/v1/push
scrape_configs:
  - job_name: systemd-journal
    journal:
      max_age: 12h
      labels:
        job: systemd-journal
      path: /var/log/journal
    relabel_configs:
      - source_labels: ["__journal__systemd_unit"]
        target_label: "unit"
      - source_labels: ["__journal__hostname"]
        target_label: host
      - source_labels: ["__journal_priority_keyword"]
        target_label: level
      - source_labels: ["__journal_syslog_identifier"]
        target_label: syslog_identifier
Promtail Config: Loki with LoadBalancer Service
server:
  http_listen_port: 9080
  grpc_listen_port: 0
positions:
  filename: /tmp/positions.yaml
clients:
  - url: http://192.168.30.242:3102/loki/api/v1/push
scrape_configs:
  - job_name: systemd-journal
    journal:
      max_age: 12h
      labels:
        job: systemd-journal
      path: /var/log/journal
    relabel_configs:
      - source_labels: ["__journal__systemd_unit"]
        target_label: "unit"
      - source_labels: ["__journal__hostname"]
        target_label: host
      - source_labels: ["__journal_priority_keyword"]
        target_label: level
      - source_labels: ["__journal_syslog_identifier"]
        target_label: syslog_identifier
Docker Compose File #
# Create Docker Compose file
sudo vi docker-compose.yml
services:
  promtail:
    image:  grafana/promtail:latest
    container_name: promtail
    volumes:
      - ./promtail.yaml:/etc/promtail/docker-config.yaml
      - /var/log/journal:/var/log/journal:ro
      - /etc/machine-id:/etc/machine-id:ro
    command: -config.file=/etc/promtail/docker-config.yaml
    restart: unless-stopped
    networks:
      - loki
networks:
  loki:
    name: loki
Change Ownership #
# Change ownership
sudo chown -R docker-system:docker /opt/promtail
Create & Start Docker Stack #
# Create & start containers
sudo -u docker-system docker compose up -d
 
Promtail Container for Containers #
Prerequisites #
Docker Network #
# Create network used for Promtail to communicate with other Docker containers
sudo docker network create promtail
Docker System User #
# Create a system user for Docker
sudo adduser --system --ingroup docker --shell /sbin/nologin docker-system
Promtail Docker Compose #
Folder Structure #
# Create folder struture
sudo mkdir -p /opt/promtail && cd /opt/promtail
Promtail Configuration #
# Create the Promtail configuration file
sudo vi promtail.yaml
Loki with NodePort Service
server:
  http_listen_port: 9080
  grpc_listen_port: 0
positions:
  filename: /tmp/positions.yaml
clients:
  - url: http://192.168.30.71:31003/loki/api/v1/push
scrape_configs:
  - job_name: docker_scrape
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
        refresh_interval: 5s
        filters:
          - name: label
            values: ["logging=promtail"]
    relabel_configs:
      - source_labels: ['__meta_docker_container_name']
        regex: '/(.*)'
        target_label: 'container'
      - source_labels: ['__meta_docker_container_log_stream']
        target_label: 'logstream'
      - source_labels: ['__meta_docker_container_label_logging_jobname']
        target_label: 'job'
Loki with LoadBalancer Service
server:
  http_listen_port: 9080
  grpc_listen_port: 0
positions:
  filename: /tmp/positions.yaml
clients:
  - url: http://192.168.30.243:3103/loki/api/v1/push
scrape_configs:
  - job_name: docker_scrape
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
        refresh_interval: 5s
        filters:
          - name: label
            values: ["logging=promtail"]
    relabel_configs:
      - source_labels: ['__meta_docker_container_name']
        regex: '/(.*)'
        target_label: 'container'
      - source_labels: ['__meta_docker_container_log_stream']
        target_label: 'logstream'
      - source_labels: ['__meta_docker_container_label_logging_jobname']
        target_label: 'job'
- Docker socket is mounted to Promtail so that it’s aware of all the docker events. It’s configured to filter only containers with docker label logging=promtail.
Docker Compose File #
# Create Docker Compose file
sudo vi docker-compose.yml
services:
  promtail:
    image:  grafana/promtail:latest
    container_name: promtail
    volumes:
      - ./promtail.yaml:/etc/promtail/docker-config.yaml
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /var/run/docker.sock:/var/run/docker.sock
    command: -config.file=/etc/promtail/docker-config.yaml
    restart: unless-stopped
    networks:
      - promtail
networks:
  promtail:
    external: true
Change Ownership #
# Change ownership
sudo chown -R docker-system:docker /opt/promtail
Create & Start Docker Stack #
# Create & start containers
sudo -u docker-system docker compose up -d
Example Container #
Folder Structure #
# Create folder struture
sudo mkdir -p /opt/some-containers && cd /opt/some-containers
Docker Compose File #
# Create Docker Compose file
sudo vi docker-compose.yml
services:
  nginx:
    container_name: container-1
    image: nginx:latest
    labels:
      logging: "promtail"
      logging_jobname: "containerlogs"
    ports:
      - 8080:80
    command: /bin/sh -c "echo 'Web-App-1 Nginx Container' > /usr/share/nginx/html/index.html && nginx -g 'daemon off;'"
    restart: unless-stopped
    networks:
      - intern
      - promtail
  apache2:
    container_name: container-2
    image: httpd:latest
    labels:
      logging: "promtail"
      logging_jobname: "containerlogs"
    ports:
      - "8081:80"
    command: >
            bash -c "echo 'Web-App-2 Apache2 Container' > /usr/local/apache2/htdocs/index.html && httpd-foreground"
    restart: unless-stopped
    networks:
      - intern
      - promtail
networks:
  promtail:
    external: true
  intern:
Change Ownership #
# Change ownership
sudo chown -R docker-system:docker /opt/some-containers
Create & Start Docker Stack #
# Create & start containers
sudo -u docker-system docker compose up -d