---
title: "Structured Logging &amp; Log Aggregation with ELK Stack"
lang: "en"
author: "Mohammad Abu Mattar"
canonical: https://mkabumattar.com/post/structured-logging-elk-stack
---

![Blog post image for Structured Logging & Log Aggregation with ELK Stack - Centralized logging for microservices with Elasticsearch, Logstash, and Kibana: structured JSON logging, the Logstash pipeline, Kibana dashboards, alerting rules, and index lifecycle policies for production.](/_astro/hero.w_mPuDHM_1ruP4H.webp)

[Home](/)›[Devtips](/devtips)›[All Categories](/devtips/categories)›[DevOps & Observability](/devtips/categories/devops--observability)

Devtips

[DevOps & Observability](/devtips/categories/devops--observability)

# Structured Logging & Log Aggregation with ELK Stack

[Mohammad Abu Mattar](/authors/mohammad-abu-mattar)Published: 21 Mar 202605 Mins read04 Mins listen

[Markdown for AI(opens in a new tab)](/post/structured-logging-elk-stack/index.md "Open the plain-Markdown version of this page, for pasting into an AI tool")

TL;DR

Centralized logging for microservices with Elasticsearch, Logstash, and Kibana: structured JSON logging, the Logstash pipeline, Kibana dashboards, alerting rules, and index lifecycle policies for production.

Series

[Observability & Monitoring](/series/observability--monitoring)2/2

[PreviousTracing Microservices with OpenTelemetry](/devtips/post/tracing-microservices-opentelemetry)

All posts in this series (2)

DevTips2

1.  [Tracing Microservices with OpenTelemetry](/devtips/post/tracing-microservices-opentelemetry)
2.  [Structured Logging & Log Aggregation with ELK StackYou are here](/devtips/post/structured-logging-elk-stack)

### Structured Logging & Log Aggregation with ELK Stack

Contents

[Why centralized logging matters](#why-centralized-logging-matters)[When services fail, where do you look first?](#when-services-fail-where-do-you-look-first)[What poor logging costs you](#what-poor-logging-costs-you)[The problem: distributed logs](#the-problem-distributed-logs)[Why per-server logs aren't enough](#why-per-server-logs-arent-enough)[The fix: the ELK stack](#the-fix-the-elk-stack)[What ELK is](#what-elk-is)[What you get](#what-you-get)[How the pieces fit together](#how-the-pieces-fit-together)[Getting started with ELK](#getting-started-with-elk)[Docker Compose setup](#docker-compose-setup)[Structured logging with JSON](#structured-logging-with-json)[Why structure the log line](#why-structure-the-log-line)[Logging from your application](#logging-from-your-application)[Logstash configuration](#logstash-configuration)[A basic pipeline](#a-basic-pipeline)[Parsing logs from several services](#parsing-logs-from-several-services)[Querying logs in Kibana](#querying-logs-in-kibana)[Creating index patterns](#creating-index-patterns)[Basic searches](#basic-searches)[More of the Kibana Query Language (KQL)](#more-of-the-kibana-query-language-kql)[Building dashboards](#building-dashboards)[A monitoring dashboard](#a-monitoring-dashboard)[Setting up alerts](#setting-up-alerts)[Alert: error rate spike](#alert-error-rate-spike)[Alert: a specific error pattern](#alert-a-specific-error-pattern)[Log retention](#log-retention)[Index lifecycle management (ILM)](#index-lifecycle-management-ilm)[Request tracing with correlation IDs](#request-tracing-with-correlation-ids)[Adding a request ID](#adding-a-request-id)[Passing the request ID between services](#passing-the-request-id-between-services)[Best practices](#best-practices)[1\. Log the right amount](#1-log-the-right-amount)[2\. Use the same field names everywhere](#2-use-the-same-field-names-everywhere)[3\. Add context to errors](#3-add-context-to-errors)[4\. Plan your indices](#4-plan-your-indices)[Wrapping up](#wrapping-up)[Resources](#resources)

## [Why centralized logging matters](#why-centralized-logging-matters)

### [When services fail, where do you look first?](#when-services-fail-where-do-you-look-first)

In a distributed system, logs scatter across servers, containers and regions. One request might touch five services. When it breaks, you’re opening log files on several machines, without the context to connect them, and losing whatever the restarted container was holding.

Centralized logging puts all of it in one searchable index, with the fields you need to correlate one request across services.

### [What poor logging costs you](#what-poor-logging-costs-you)

-   **Slow debugging**: 30+ minutes to find what went wrong 5 minutes ago
-   **Lost logs**: Container restarts = logs disappear and are never recovered
-   **No correlation**: Can’t trace a request across multiple services
-   **Manual hunting**: SSH + grep through millions of lines
-   **No alerting**: You wake up to customer complaints, not alerts

## [The problem: distributed logs](#the-problem-distributed-logs)

### [Why per-server logs aren’t enough](#why-per-server-logs-arent-enough)

/var/log/app.log

```
2026-03-21 10:15:23 Error: Database connection refused
# Server 2: /var/log/app.log (you don't see this for 15 minutes)2026-03-21 10:15:22 Error: Database connection refused
# Server 3: Combined, these tell a story, but:# - They're on 3 different machines# - You can't search them together# - Container restart and logs are gone# - You have no context (which user? which request?)
```

## [The fix: the ELK stack](#the-fix-the-elk-stack)

### [What ELK is](#what-elk-is)

-   **Elasticsearch**: Distributed search and analytics engine. Stores logs as searchable documents with full-text indexing.
-   **Logstash**: Log processing pipeline. Collects, parses, enriches, and routes logs to Elasticsearch.
-   **Kibana**: Visualization and exploration platform. Query logs with SQL-like syntax, build dashboards, set alerts.

### [What you get](#what-you-get)

-   **Centralized**: All logs in one place, searchable in milliseconds
-   **Scalable**: Handles billions of logs without slowdown
-   **Structured**: JSON-based searching and filtering
-   **Correlated**: Trace requests across multiple services
-   **Persistent**: No data loss when services restart
-   **Alertable**: Triggered notifications on patterns

## [How the pieces fit together](#how-the-pieces-fit-together)

```
1Services → Filebeat/Logstash → Elasticsearch ← Kibana (Query/Visualize)2 ↓           ↓                    ↓3App logs    Parse, enrich        Index, store, analyze4DB logs     Filter, route        Full-text search5System logs Add context          Real-time updates
```

## [Getting started with ELK](#getting-started-with-elk)

### [Docker Compose setup](#docker-compose-setup)

compose.yml

```
1services:2  elasticsearch:3    image: docker.elastic.co/elasticsearch/elasticsearch:8.11.04    container_name: elasticsearch5    environment:6      discovery.type: single-node7      xpack.security.enabled: false8      xpack.security.transport.ssl.enabled: false9    ports:10      - '9200:9200'11    volumes:12      - elasticsearch-data:/usr/share/elasticsearch/data13
14  kibana:15    image: docker.elastic.co/kibana/kibana:8.11.016    container_name: kibana17    ports:18      - '5601:5601'19    environment:20      ELASTICSEARCH_HOSTS: http://elasticsearch:920021    depends_on:22      - elasticsearch23
24  logstash:25    image: docker.elastic.co/logstash/logstash:8.11.026    container_name: logstash27    volumes:28      - ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf29    ports:30      - '5000:5000'31    environment:32      discovery.seed_hosts: elasticsearch33      LS_JAVA_OPTS: '-Xmx256m -Xms256m'34    depends_on:35      - elasticsearch36
37volumes:38  elasticsearch-data:
```

Start the stack:

Terminal window

```
docker-compose up -d# Kibana available at http://localhost:5601# Elasticsearch at http://localhost:9200
```

## [Structured logging with JSON](#structured-logging-with-json)

### [Why structure the log line](#why-structure-the-log-line)

```
1// Good: Structured (searchable, filterable)2{"timestamp": "2026-03-21T10:15:23Z", "service": "user-api", "level": "ERROR", "message": "Database connection failed", "user_id": 42, "request_id": "req-abc-123", "error_code": "DB_CONN_REFUSED", "retry_count": 3}3
4// Bad: Unstructured (exact string matching only)5"2026-03-21 10:15:23 ERROR [user-api] Database connection failed for user 42 in request req-abc-123"
```

### [Logging from your application](#logging-from-your-application)

**Python:**

app.py

```
1import json2import logging3from pythonjsonlogger import jsonlogger4
5# Configure JSON logging6logHandler = logging.StreamHandler()7formatter = jsonlogger.JsonFormatter()8logHandler.setFormatter(formatter)9logger = logging.getLogger()10logger.addHandler(logHandler)11logger.setLevel(logging.INFO)12
13# Use logging with context14logger.info("User login", extra={15    "user_id": 42,16    "request_id": "req-abc-123",17    "service": "user-api",18    "ip_address": "192.168.1.1"19})20
21logger.error("Database connection failed", extra={22    "user_id": 42,23    "request_id": "req-abc-123",24    "service": "user-api",25    "error_code": "DB_CONN_REFUSED",26    "retry_count": 327})
```

**Node.js:**

app.ts

```
1import winston from 'winston';2
3const logger = winston.createLogger({4  format: winston.format.json(),5  defaultMeta: {service: 'api-gateway'},6  transports: [new winston.transports.Console()],7});8
9// Log with context10logger.info('User authenticated', {11  user_id: 42,12  request_id: 'req-abc-123',13  ip_address: '192.168.1.1',14});15
16logger.error('Database connection failed', {17  user_id: 42,18  request_id: 'req-abc-123',19  error_code: 'DB_CONN_REFUSED',20  retry_count: 3,21});
```

## [Logstash configuration](#logstash-configuration)

### [A basic pipeline](#a-basic-pipeline)

logstash.conf

```
1input {2  tcp {3    port => 50004    codec => json5  }6
7  # Read from files8  file {9    path => "/var/log/app/*.log"10    codec => json11  }12}13
14filter {15  # Parse and enrich logs16  if [service] == "api-gateway" {17    mutate {18      add_field => { "service_tier" => "frontend" }19    }20  }21
22  # Extract request ID from logs for correlation23  grok {24    match => { "message" => "request_id=%{NOTSPACE:request_id}" }25  }26
27  # Add timestamp if missing28  date {29    match => [ "timestamp", "ISO8601" ]30    target => "@timestamp"31  }32}33
34output {35  elasticsearch {36    hosts => ["elasticsearch:9200"]37    index => "logs-%{+YYYY.MM.dd}"38  }39
40  # Also output to stdout for debugging41  stdout {42    codec => rubydebug43  }44}
```

### [Parsing logs from several services](#parsing-logs-from-several-services)

logstash-advanced.conf

```
1input {2  tcp {3    port => 50004    codec => json5  }6}7
8filter {9  # Normalize service names10  translate {11    field => "service"12    destination => "service_normalized"13    dictionary => {14      "user-api" => "user-service"15      "user_api" => "user-service"16      "users" => "user-service"17    }18  }19
20  # Add environment if not present21  if ![environment] {22    mutate {23      add_field => { "environment" => "production" }24    }25  }26
27  # Parse error stack traces28  if [level] == "ERROR" and [stack_trace] {29    mutate {30      split => { "stack_trace" => "\n" }31    }32  }33}34
35output {36  elasticsearch {37    hosts => ["elasticsearch:9200"]38    index => "logs-%{environment}-%{+YYYY.MM.dd}"39  }40}
```

## [Querying logs in Kibana](#querying-logs-in-kibana)

### [Creating index patterns](#creating-index-patterns)

In Kibana:

1.  Go to **Stack Management** → **Index Patterns**
2.  Create pattern: `logs-*` (matches `logs-2026.03.21`, etc.)
3.  Set timestamp field to `@timestamp`

### [Basic searches](#basic-searches)

```
1# Find all ERROR logs2level: ERROR3
4# Errors in specific service5level: ERROR AND service: "user-api"6
7# Errors for specific user8level: ERROR AND user_id: 429
10# Errors in time range (last 1 hour)11level: ERROR AND @timestamp: [now-1h TO now]12
13# Request tracing across services14request_id: "req-abc-123"
```

### [More of the Kibana Query Language (KQL)](#more-of-the-kibana-query-language-kql)

```
1# Multiple conditions2service: "user-api" AND level: "ERROR" AND response_time_ms > 10003
4# Wildcard matching5service: "user-*" AND message: "*connection*"6
7# Range queries8http_status_code: [400 TO 599] AND @timestamp: [now-1d/d TO now]9
10# Logical operators11(service: "payment-api" OR service: "billing-api") AND level: "ERROR"12
13# Exists14error_trace:*
```

## [Building dashboards](#building-dashboards)

### [A monitoring dashboard](#a-monitoring-dashboard)

```
1Dashboard: "Microservices Health"2
31. **Error Rate Panel** (Line chart)4   - Query: level: "ERROR"5   - Group by: service (X-axis), time (series)6   - Show: errors per minute7
82. **Response Time Panel** (Bar chart)9   - Query: All logs10   - Metric: avg(response_time_ms)11   - Breakdown by: service12
133. **Top Errors Panel** (Table)14   - Query: level: "ERROR"15   - Top 10: error_code16
174. **Request Volume Panel** (Metric)18   - Query: All logs19   - Show: total request count
```

## [Setting up alerts](#setting-up-alerts)

### [Alert: error rate spike](#alert-error-rate-spike)

```
1# In Kibana: Stack Management → Alerting → Create Rule2
3Condition:4  When: average(level: "ERROR") is greater than 1005  For: the last 5 minutes6
7Action:8  Webhook: POST to Slack channel9  Message: "Error rate spiked in production"
```

### [Alert: a specific error pattern](#alert-a-specific-error-pattern)

```
1Condition:2  When: count(error_code: "DB_CONN_REFUSED") is greater than 103  For: the last 2 minutes4
5Action:6  Send to PagerDuty7  Message: "Database connection failures detected"
```

## [Log retention](#log-retention)

### [Index lifecycle management (ILM)](#index-lifecycle-management-ilm)

```
1{2  "policy": "logs-policy",3  "phases": {4    "hot": {5      "min_age": "0d",6      "actions": {7        "rollover": {8          "max_primary_store_size": "50GB",9          "max_age": "1d"10        }11      }12    },13    "warm": {14      "min_age": "7d",15      "actions": {16        "set_replicas": {17          "number_of_replicas": 118        }19      }20    },21    "cold": {22      "min_age": "30d",23      "actions": {24        "searchable_snapshot": {25          "snapshot_repository": "my_repository"26        }27      }28    },29    "delete": {30      "min_age": "90d",31      "actions": {32        "delete": {}33      }34    }35  }36}
```

## [Request tracing with correlation IDs](#request-tracing-with-correlation-ids)

### [Adding a request ID](#adding-a-request-id)

request\_id\_middleware.py

```
1from fastapi import Request2import uuid3import logging4
5logger = logging.getLogger(__name__)6
7async def add_request_id(request: Request, call_next):8    # Generate or extract request ID9    request_id = request.headers.get("X-Request-ID") or str(uuid.uuid4())10
11    # Store in request state12    request.state.request_id = request_id13
14    # Log with correlation15    logger.info("Request started", extra={16        "request_id": request_id,17        "method": request.method,18        "path": request.url.path19    })20
21    response = await call_next(request)22
23    # Add to response headers for client24    response.headers["X-Request-ID"] = request_id25
26    return response
```

### [Passing the request ID between services](#passing-the-request-id-between-services)

```
1# When calling another service2import httpx3
4async def call_user_service(request):5    request_id = request.state.request_id6
7    async with httpx.AsyncClient() as client:8        response = await client.get(9            "http://user-api/users/42",10            headers={"X-Request-ID": request_id}  # Pass it along11        )12
13    return response.json()
```

## [Best practices](#best-practices)

### [1\. Log the right amount](#1-log-the-right-amount)

```
1# Good: Structured context without redundancy2logger.info("Payment processed", extra={3    "user_id": 42,4    "request_id": "req-abc",5    "amount": 99.99,6    "currency": "USD"7})8
9# Bad: Too verbose10logger.info(f"User with ID 42 has processed a payment of 99.99 USD via request req-abc at {timestamp}")
```

### [2\. Use the same field names everywhere](#2-use-the-same-field-names-everywhere)

```
1// Across all services, use same field names2{3  "timestamp": "2026-03-21T10:15:23Z",4  "level": "ERROR",5  "service": "user-api",6  "user_id": 42,7  "request_id": "req-abc"8}
```

### [3\. Add context to errors](#3-add-context-to-errors)

```
1try:2    result = db.query(...)3except Exception as e:4    logger.error("Database query failed", extra={5        "error_type": type(e).__name__,6        "error_message": str(e),7        "query": query,  # What failed?8        "user_id": user_id,  # Who was affected?9        "request_id": request_id  # Trace it10    })
```

### [4\. Plan your indices](#4-plan-your-indices)

```
1# Keep recent data hot (highly available)2# Archive old data (cost-effective)3# Delete after retention period4
5Daily indices: logs-2026.03.21, logs-2026.03.226Retention: 90 days hot + searchable, 1 year archival, then delete
```

## [Wrapping up](#wrapping-up)

**One searchable index turns a half-hour of log hunting into a query.**

The part that pays for itself is structured JSON with a request ID in every line. Do that before you touch Kibana, because a centralized pile of unstructured strings is still a pile. Add OpenTelemetry traces alongside it and you have both halves: logs for what happened inside a service, traces for the path between them.

## [Resources](#resources)

-   [Elasticsearch Documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html)
-   [Kibana Advanced Query Language](https://www.elastic.co/guide/en/kibana/current/kuery-query-language.html)
-   [Logstash Filter Guide](https://www.elastic.co/guide/en/logstash/current/filter-plugins.html)
-   [Index Lifecycle Management](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-lifecycle-management.html)

Was this useful?

## Tags

[#Logging](/devtips/tags/logging)[#ELK Stack](/devtips/tags/elk-stack)[#Elasticsearch](/devtips/tags/elasticsearch)[#Kibana](/devtips/tags/kibana)[#Microservices](/devtips/tags/microservices)[#Observability](/devtips/tags/observability)[#Monitoring](/devtips/tags/monitoring)

## Share

[Facebook](https://facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fstructured-logging-elk-stack "Share on Facebook")[Twitter](https://twitter.com/intent/tweet/?text=Structured%20Logging%20%26%20Log%20Aggregation%20with%20ELK%20Stack&url=https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fstructured-logging-elk-stack "Share on Twitter")[LinkedIn](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fstructured-logging-elk-stack&title=Structured%20Logging%20%26%20Log%20Aggregation%20with%20ELK%20Stack&summary=Centralized%20logging%20for%20microservices%20with%20Elasticsearch%2C%20Logstash%2C%20and%20Kibana%3A%20structured%20JSON%20logging%2C%20the%20Logstash%20pipeline%2C%20Kibana%20dashboards%2C%20alerting%20rules%2C%20and%20index%20lifecycle%20policies%20for%20production.&source=https://mkabumattar.com "Share on LinkedIn")[WhatsApp](https://wa.me/?text=Structured%20Logging%20%26%20Log%20Aggregation%20with%20ELK%20Stack%20https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fstructured-logging-elk-stack "Share on WhatsApp")[Telegram](https://t.me/share/url?url=https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fstructured-logging-elk-stack&text=Structured%20Logging%20%26%20Log%20Aggregation%20with%20ELK%20Stack "Share on Telegram")[Reddit](https://www.reddit.com/submit?url=https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fstructured-logging-elk-stack&title=Structured%20Logging%20%26%20Log%20Aggregation%20with%20ELK%20Stack "Share on Reddit")[Hacker News](http://news.ycombinator.com/submitlink?u=https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fstructured-logging-elk-stack&t=Structured%20Logging%20%26%20Log%20Aggregation%20with%20ELK%20Stack "Share on Hacker News")[Pinterest](https://pinterest.com/pin/create/button/?url=https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fstructured-logging-elk-stack&media=&description=Centralized%20logging%20for%20microservices%20with%20Elasticsearch%2C%20Logstash%2C%20and%20Kibana%3A%20structured%20JSON%20logging%2C%20the%20Logstash%20pipeline%2C%20Kibana%20dashboards%2C%20alerting%20rules%2C%20and%20index%20lifecycle%20policies%20for%20production. "Share on Pinterest")[Email](<mailto:?subject=Structured%20Logging%20%26%20Log%20Aggregation%20with%20ELK%20Stack&body=Check out this article: https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fstructured-logging-elk-stack>)

## Comments

## You might also enjoy

More posts on similar topics

[![Tracing Microservices with OpenTelemetry](/_astro/hero.BOHz8WyH_Z1IEpz3.webp)](/devtips/post/tracing-microservices-opentelemetry)

## [Tracing Microservices with OpenTelemetry](/devtips/post/tracing-microservices-opentelemetry)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [Observability & Monitoring](/devtips/categories/observability--monitoring)

Why monitor your microservices? The complexity of distributed systems If you're juggling multiple services, it's hard to track how they work together. OpenTelemetry lets you follow one reques

[#OpenTelemetry](/devtips/tags/opentelemetry)[#Microservices](/devtips/tags/microservices)[#Distributed Tracing](/devtips/tags/distributed-tracing)+4 tags

[read more](/devtips/post/tracing-microservices-opentelemetry)

[![Helm Charts: Templating & Multi-Environment Kubernetes Deployments](/_astro/hero.C-pWunDw_Z143yJc.webp)](/devtips/post/helm-charts-kubernetes-multi-environment)

## [Helm Charts: Templating & Multi-Environment Kubernetes Deployments](/devtips/post/helm-charts-kubernetes-multi-environment)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [Kubernetes & DevOps](/devtips/categories/kubernetes--devops)

Why Helm matters The Kubernetes manifest problem Managing Kubernetes manifests at scale becomes a nightmare. You have a deployment for dev, staging and production. Each one is 90% identi

[#Helm](/devtips/tags/helm)[#Kubernetes](/devtips/tags/kubernetes)[#Deployment](/devtips/tags/deployment)+4 tags

[read more](/devtips/post/helm-charts-kubernetes-multi-environment)

[![Container Image Vulnerability Scanning in CI/CD with Trivy](/_astro/hero.yY1orHlw_2oq3jw.webp)](/devtips/post/container-image-vulnerability-scanning-trivy)

## [Container Image Vulnerability Scanning in CI/CD with Trivy](/devtips/post/container-image-vulnerability-scanning-trivy)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [DevOps & DevSecOps](/devtips/categories/devops--devsecops)

Why container security matters Where the vulnerabilities hide A container image is one of the largest pieces of untrusted code you ship. Every image you build carries the base OS layer,

[#Container Security](/devtips/tags/container-security)[#Vulnerability Scanning](/devtips/tags/vulnerability-scanning)[#Trivy](/devtips/tags/trivy)+4 tags

[read more](/devtips/post/container-image-vulnerability-scanning-trivy)

[![Policy-as-Code Governance with OPA/Rego](/_astro/hero.CwAJ64Mi_1WeH9p.webp)](/devtips/post/policy-as-code-opa-rego)

## [Policy-as-Code Governance with OPA/Rego](/devtips/post/policy-as-code-opa-rego)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [DevOps & DevSecOps](/devtips/categories/devops--devsecops)

Why policy-as-code matters The governance problem Managing infrastructure at scale gets complicated fast. As your infrastructure grows, keeping it consistent and compliant gets harder. M

[#Policy as Code](/devtips/tags/policy-as-code)[#OPA/Rego](/devtips/tags/oparego)[#Compliance](/devtips/tags/compliance)+4 tags

[read more](/devtips/post/policy-as-code-opa-rego)

[![Managing Terraform at Scale with Terragrunt](/_astro/hero.DUZZoi07_ZRPUOh.webp)](/devtips/post/terraform-terragrunt-wrappers)

## [Managing Terraform at Scale with Terragrunt](/devtips/post/terraform-terragrunt-wrappers)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [Cloud & Infrastructure Automation](/devtips/categories/cloud--infrastructure-automation)

The problem with Terraform at scale Duplicated code across environments If you're managing infrastructure with Terraform across several environments or projects, you've probably hit the point

[#Terraform](/devtips/tags/terraform)[#Terragrunt](/devtips/tags/terragrunt)[#Infrastructure as Code](/devtips/tags/infrastructure-as-code)+4 tags

[read more](/devtips/post/terraform-terragrunt-wrappers)

[![Setting Up GitHub Copilot Agent Skills in Your Repository](/_astro/hero.CCW95PMK_ZaDUPy.webp)](/devtips/post/github-copilot-agent-skills-setup)

## [Setting Up GitHub Copilot Agent Skills in Your Repository](/devtips/post/github-copilot-agent-skills-setup)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [Developer Tools & Productivity](/devtips/categories/developer-tools--productivity)

Why teach Copilot new skills? What a skill actually is If you're ready to teach Copilot some new tricks, Agent Skills are your answer. Built on the agentskills.io op

[#GitHub Copilot](/devtips/tags/github-copilot)[#AI Tools](/devtips/tags/ai-tools)[#VS Code](/devtips/tags/vs-code)+4 tags

[read more](/devtips/post/github-copilot-agent-skills-setup)

6 related posts
