Monitor an EC2 Instance with Grafana

Deploy Prometheus and Grafana on EC2 to visualize live server metrics.

Introduction

30 Second Summary

When your favorite app goes down or slows to a crawl, someone on the engineering team usually saw it coming. They spotted the problem on a monitoring dashboard that tracks the server's vital signs in real time.

In this project, you will build a server monitoring system on an AWS EC2 instance that collects CPU, memory, and disk metrics every 15 seconds and displays them on a live dashboard.

What You'll Build

A complete monitoring stack running Prometheus, Grafana, and Node Exporter on an AWS EC2 instance, orchestrated with Docker Compose.

By the end of this project, you'll have:

  • A running EC2 instance with Docker installed and a three-service monitoring stack deployed from a single configuration file.
  • Hands-on experience writing PromQL queries to calculate CPU usage, memory availability, and disk space from real time-series data.
  • A custom Grafana dashboard with live-updating panels showing your server's health, plus an imported professional dashboard used by production teams.
  • Secret Mission: add alerting rules that fire when CPU usage exceeds a threshold.

Are there any prerequisites?

You need an AWS account and a code editor like VS Code. No prior experience with Prometheus, Grafana, or Docker is required.

Launch an EC2 Instance and Install Docker

Before you can monitor anything, you need a server to monitor. In this step, you will launch a cloud server on AWS and install the tools that will run your monitoring stack.

You will use Docker to run Prometheus, Grafana, and Node Exporter as containers on an EC2 instance. Docker packages each tool into an isolated environment so you do not have to worry about version conflicts or complex installations.

In this step, get ready to:
  • Launch an Ubuntu EC2 instance with the right security group settings.
  • Connect to your instance via SSH and install Docker Engine.
  • Verify Docker Compose is available and ready to orchestrate containers.
Launch an Ubuntu EC2 instance

Your monitoring stack needs a server with enough memory to run three containers simultaneously. A t3.small instance gives you 2 GB of RAM, which provides comfortable headroom.

  • Sign into the AWS Management Console.
  • In the search bar at the top, type EC2 and select EC2 from the results.
  • Click Launch instance.

Configure your instance with these settings:

  • Under Name and tags, enter prometheus-grafana-lab.
  • Under Application and OS Images, select Ubuntu.
  • Choose Ubuntu Server 24.04 LTS from the AMI dropdown (free tier eligible).
  • Under Instance type, select t3.small.

Why t3.small instead of the free tier t2.micro?

The monitoring stack (Prometheus + Grafana + Node Exporter) needs around 1.2-1.5 GB RAM under load. The t2.micro only has 1 GB, which causes swapping and poor performance.

The t3.small costs approximately $0.02/hour. For a 2-3 hour project session, that is around $0.05 total.

Now configure your key pair and network settings:

  • Under Key pair (login), click Create new key pair.
  • Enter a name for your key pair (e.g. monitoring-lab-key).
  • Leave Key pair type as RSA and Private key file format as .pem.
  • Click Create key pair. Your browser will download a .pem file. Save it somewhere you can find it later.
  • Under Network settings, click Edit.
  • Confirm that Allow SSH traffic is checked and the source is set to My IP.
  • Click Add security group rule to add a second rule.
  • Set Type to Custom TCP, Port range to 9090, and Source to My IP.
  • Click Add security group rule again to add a third rule.
  • Set Type to Custom TCP, Port range to 3000, and Source to My IP.

What are these ports for?

Port 22 is for SSH (how you connect to the server). Port 9090 is for the Prometheus web UI. Port 3000 is for the Grafana dashboard.

Setting the source to My IP means only your computer can reach these ports. This keeps your monitoring stack private.

  • Leave all other settings as defaults.
  • Click Launch instance.
  • Click View all instances to return to the Instances list.
  • Wait for your instance's Instance state to show Running and the Status check to show 2/2 checks passed. This takes 1-2 minutes.
  • Select your instance and note the Public IPv4 address shown in the details pane below. You will use this IP throughout the project.

Instance stuck in Pending?

Most instances start within 60 seconds. If yours stays in Pending for more than 5 minutes, try refreshing the page. Make sure you selected a region that supports t3.small instances.

Help me troubleshoot my EC2 instance launch

Connect via SSH and install Docker

With your instance running, you need to connect to it from your Windows machine using SSH. This gives you a terminal session on the remote server where you can install Docker.

  • Press the Windows key to open your search bar.
  • Type PowerShell and press Enter to open it.
  • Connect to your EC2 instance by running this command:
ssh -i [[PEMPATH="path\to\your-key.pem"]] ubuntu@[[EC2_PUBLIC_IP="your EC2 public IPv4 address"]]

What does this command do?

The ssh command opens a secure connection to your remote server. The -i flag points to your private key file that proves your identity. The ubuntu@ part is the default username on Ubuntu EC2 instances.

  • If prompted "Are you sure you want to continue connecting?", type yes and press Enter.

You should see a welcome message from Ubuntu and your prompt should change to something like ubuntu@ip-...:~$. You are now on your EC2 instance.

Getting a permissions error on your .pem file?

If SSH warns that your key file permissions are too open, you need to restrict access to the file. In PowerShell, right-click the .pem file in File Explorer, go to Properties > Security > Advanced, disable inheritance, and remove all users except your own account.

Help me fix SSH permissions on Windows

Now install Docker Engine using the official apt repository. This involves adding Docker's GPG key and repository, then installing the packages.

  • Set up Docker's apt repository by running these commands:
# Add Docker's official GPG key
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

sudo apt update

What does this code do?

These commands download Docker's official signing key and add Docker's package repository to your system. This tells Ubuntu where to find the official Docker packages when you run apt install next.

  • Install the Docker packages by running:
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

What does this command do?

This installs Docker Engine (docker-ce), the CLI (docker-ce-cli), the container runtime (containerd.io), and two plugins: Buildx for building images and Docker Compose for orchestrating multi-container apps.

  • When prompted to continue with the installation, type Y and press Enter.
  • Add your user to the docker group so you can run Docker commands without sudo:
sudo usermod -aG docker $USER
  • Apply the group change without logging out by running:
newgrp docker
  • Verify Docker works by running the hello-world test container:
docker run hello-world

You should see a message that says "Hello from Docker!" followed by an explanation of what just happened. This confirms Docker is installed and running correctly.

Seeing permission denied?

If you see a "permission denied" error when running docker run hello-world, the group change has not taken effect. Try logging out of your SSH session and logging back in. Run exit, then reconnect with the same ssh command you used earlier.

Help me fix Docker permissions on my EC2 instance

Verify Docker Compose is available

Docker Compose lets you define and run multiple containers with a single YAML file. It was installed as a plugin alongside Docker Engine in the previous step.

  • Confirm Docker Compose is available by running:
docker compose version

You should see a version number printed (e.g. Docker Compose version v2.x.x). This confirms the Compose plugin is ready.

What is Docker Compose?

Docker Compose is a tool for defining multi-container applications in a single file called docker-compose.yml. Instead of starting each container separately, you run one command and all your services start together.

In the next step, you will use Docker Compose to launch Prometheus, Grafana, and Node Exporter simultaneously with shared networking.

Command not found?

Make sure you installed the docker-compose-plugin package in the earlier step. You can install it by running sudo apt install docker-compose-plugin. Note: the command is docker compose (with a space), not docker-compose (with a hyphen).

Help me fix Docker Compose not being found

Your cloud server is running and Docker is ready. Next up, you will deploy your entire monitoring stack with a single Docker Compose command.

Deploy the Monitoring Stack

Your EC2 instance is up and Docker is installed. Now it's time to deploy the three services that form your monitoring stack: Prometheus, Grafana, and Node Exporter.

Docker Compose lets you define all three services in a single YAML file and start them with one command. Without it, you'd need to run three separate docker run commands with dozens of flags each.

In this step, get ready to:
  • Create a Prometheus configuration file that defines what to monitor.
  • Write a Docker Compose file that orchestrates three containers.
  • Start the monitoring stack and verify all services are healthy.
Create the project directory and configuration files

Prometheus needs a configuration file to know which services to monitor and how often to check them. You'll create a project folder on your EC2 instance and add this configuration.

  • In your SSH session, create the project folder and move into it by running these commands:
mkdir ~/monitoring && cd ~/monitoring
  • Confirm you're in the right directory by running:
pwd

You should see /home/ubuntu/monitoring in the output.

  • Create the Prometheus configuration file by running:
nano prometheus.yml
  • Paste the following configuration into the editor:
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["prometheus:9090"]

  - job_name: "node"
    static_configs:
      - targets: ["node-exporter:9100"]

What does this configuration do?

The global section sets the scrape interval to 15 seconds. This means Prometheus will pull metrics from each target every 15 seconds.

The scrape_configs section defines two jobs. The prometheus job monitors Prometheus itself at port 9090. The node job monitors Node Exporter at port 9100, which exposes system metrics like CPU and memory.

  • Save the file by pressing Ctrl+O, then press Enter to confirm the filename.
  • Exit nano by pressing Ctrl+X.
  • Verify the file was created by running:
cat prometheus.yml

You should see the full YAML configuration you just pasted printed back in the terminal.

File is empty or missing?

Make sure you pressed Ctrl+O (the letter O, not zero) followed by Enter before exiting. If the file is missing, confirm you're in the ~/monitoring directory with pwd and try again.

Help me troubleshoot my prometheus.yml file creation

✔️ Awesome, I've got everything!

Great. Double check you've saved your file and the content matches what's shown above.

ⓧ I'd like to double check the full code

global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["prometheus:9090"]

  - job_name: "node"
    static_configs:
      - targets: ["node-exporter:9100"]
Create the Docker Compose file

The Docker Compose file defines all three services, their ports, volumes, and how they connect to each other. One file replaces dozens of manual docker run flags.

  • Create the Docker Compose file by running:
nano docker-compose.yml
  • Paste the following configuration into the editor:
services:
  prometheus:
    image: prom/prometheus:v3.12.0
    container_name: prometheus
    restart: unless-stopped
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
      - "--storage.tsdb.path=/prometheus"
      - "--storage.tsdb.retention.time=7d"
    networks:
      - monitoring

  grafana:
    image: grafana/grafana-oss:13.1.0
    container_name: grafana
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana-data:/var/lib/grafana
    networks:
      - monitoring
    depends_on:
      - prometheus

  node-exporter:
    image: prom/node-exporter:v1.11.1
    container_name: node-exporter
    restart: unless-stopped
    ports:
      - "9100:9100"
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - "--path.procfs=/host/proc"
      - "--path.sysfs=/host/sys"
      - "--path.rootfs=/rootfs"
      - "--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)"
    networks:
      - monitoring

volumes:
  prometheus-data:
  grafana-data:

networks:
  monitoring:
    driver: bridge

What does this Docker Compose file do?

  • prometheus runs Prometheus v3.12.0 on port 9090. It mounts your prometheus.yml config file and stores metric data in a named volume so data survives container restarts.
  • grafana runs Grafana OSS 13.1.0 on port 3000. It depends on Prometheus so it starts after Prometheus is ready.
  • node-exporter runs Node Exporter v1.11.1 on port 9100. It mounts the host's /proc, /sys, and / as read-only so it can read real system metrics from inside the container.
  • All three services share a monitoring bridge network. This lets them communicate using service names (like prometheus:9090) instead of IP addresses.
  • Save the file by pressing Ctrl+O, then press Enter to confirm.
  • Exit nano by pressing Ctrl+X.
  • Verify both files exist by running:
ls

You should see docker-compose.yml and prometheus.yml in the output.

Only see one file?

If docker-compose.yml is missing, you may have accidentally saved it outside the ~/monitoring folder. Run pwd to confirm you're in /home/ubuntu/monitoring, then run nano docker-compose.yml again.

Help me troubleshoot my Docker Compose file

✔️ Awesome, I've got everything!

Great. Double check you've saved your file and the content matches what's shown above.

ⓧ I'd like to double check the full code

services:
  prometheus:
    image: prom/prometheus:v3.12.0
    container_name: prometheus
    restart: unless-stopped
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
      - "--storage.tsdb.path=/prometheus"
      - "--storage.tsdb.retention.time=7d"
    networks:
      - monitoring

  grafana:
    image: grafana/grafana-oss:13.1.0
    container_name: grafana
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana-data:/var/lib/grafana
    networks:
      - monitoring
    depends_on:
      - prometheus

  node-exporter:
    image: prom/node-exporter:v1.11.1
    container_name: node-exporter
    restart: unless-stopped
    ports:
      - "9100:9100"
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - "--path.procfs=/host/proc"
      - "--path.sysfs=/host/sys"
      - "--path.rootfs=/rootfs"
      - "--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)"
    networks:
      - monitoring

volumes:
  prometheus-data:
  grafana-data:

networks:
  monitoring:
    driver: bridge
Start the stack and verify

Both configuration files are in place. One command will pull the container images and start all three services.

  • Start the entire monitoring stack by running:
docker compose up -d

What does this command do?

The -d flag runs the containers in detached mode, meaning they run in the background and give you back your terminal. Docker pulls the images for Prometheus, Grafana, and Node Exporter, then starts all three containers.

The first run takes 1-2 minutes as Docker downloads the container images. You'll see progress bars for each image layer being pulled.

  • Once the command finishes, check that all three containers are running:
docker compose ps

You should see three containers listed with a STATUS of Up: prometheus, grafana, and node-exporter.

Seeing a container in 'Exited' or 'Restarting' state?

Run docker compose logs prometheus (replacing prometheus with the failing container's name) to see its error log. The most common cause is a YAML indentation error in prometheus.yml or docker-compose.yml. Open the file with nano and compare it against the double-check tab above.

Help me fix my Docker Compose containers

  • Open your web browser and navigate to the Prometheus UI:
http://[[EC2_PUBLIC_IP="your EC2 public IPv4 address"]]:9090
  • In the Prometheus UI, click Status in the top navigation bar.
  • Click Targets from the dropdown menu.

You should see two target groups listed: prometheus and node. Both should show a green UP state.

What does UP mean?

An UP state means Prometheus successfully scraped metrics from that target on its last attempt. If you see DOWN, it means Prometheus couldn't reach the target. Since all services share the same Docker network, they can find each other by service name.

Can't reach the Prometheus UI in your browser?

Make sure you're using the EC2 instance's public IPv4 address (not the private IP). You can find it in the AWS Console under EC2 > Instances.

Also confirm that your security group allows inbound traffic on port 9090 from your IP address. If you recently changed networks (e.g. switched Wi-Fi), your IP may have changed.

Help me access the Prometheus UI on my EC2 instance

Your monitoring stack is live and collecting data. Next up, you'll query these metrics directly using Prometheus's query language, PromQL.

Explore Prometheus and Write PromQL Queries

Your monitoring stack is running and collecting data every 15 seconds. Raw metrics are flowing in, but numbers on their own don't tell you much.

In this step, you'll use PromQL (Prometheus Query Language) to ask questions about your server's health. You'll turn raw counters into meaningful answers like "how busy is my CPU?" and "how much disk space is left?"

In this step, get ready to:
  • Run basic PromQL queries to check target health and view raw metrics.
  • Use PromQL functions to calculate CPU usage rate, memory availability, and disk space.
Run your first queries in the Prometheus UI

The Prometheus web UI includes an expression browser where you type PromQL queries and see results instantly. It has two views: the Table tab shows a snapshot of current values, and the Graph tab plots values over time.

  • Return to the Prometheus UI in your browser at http://your-ec2-ip:9090.
  • Click the query input box at the top of the page.
  • Type up into the expression box and click Execute.

You should see two results in the Table tab, each with a value of 1. One has the label job="prometheus" and the other has job="node".

What does the up query tell you?

The up metric is a built-in health check. A value of 1 means Prometheus successfully scraped that target on its last attempt. A value of 0 means the scrape failed.

This is the simplest query you can run, and it's the first thing to check when debugging a monitoring setup.

  • Clear the query box, type node_cpu_seconds_total, and click Execute.

You should see many rows of results. Each row represents a different CPU mode (idle, user, system, iowait, and others) with a large number that keeps growing.

Why are these numbers so large?

The node_cpu_seconds_total metric is a counter. It tracks the total number of CPU seconds spent in each mode since the server started. Counters only go up.

A raw counter isn't very useful on its own. You need PromQL functions like rate() to turn these ever-increasing numbers into something meaningful, like "how fast is this counter growing right now?"

Seeing no results or an error?

Make sure you typed the metric name exactly, including the underscores. PromQL is case-sensitive.

If you see zero results, check that both targets are still UP by navigating to Status > Targets in the Prometheus menu bar.

Help me debug why my Prometheus query returns no results.

Use PromQL functions for meaningful metrics

Raw counters tell you how much total work has happened. PromQL functions transform them into real-time rates and percentages that answer practical questions about your server.

  • Clear the query box and enter this expression to see how quickly idle CPU time is accumulating:
rate(node_cpu_seconds_total{mode="idle"}[5m])

Click Execute. You should see one or two results (one per CPU core) with decimal values between 0 and 1.

What does rate() do?

The rate() function calculates how fast a counter is increasing per second, averaged over a time window. Here, [5m] means "look at the last 5 minutes of data."

A result of 0.95 for idle mode means the CPU spent 95% of its time doing nothing. A lower idle value means higher CPU usage.

  • Check available memory as a percentage by entering this expression and clicking Execute:
node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes * 100

You should see a single value representing the percentage of memory currently available on your EC2 instance. On a fresh t3.small with the monitoring stack running, this is typically between 40-60%.

What's happening in this query?

This query divides the available memory by total memory and multiplies by 100 to get a percentage. PromQL supports arithmetic operators just like a calculator, letting you combine metrics mathematically.

  • View available disk space on the root filesystem by entering this expression and clicking Execute:
node_filesystem_avail_bytes{mountpoint="/"}

You should see a large number representing the available bytes on the root filesystem. This value is in bytes, so a result like 5000000000 means roughly 5 GB free.

What are label filters?

The {mountpoint="/"} part is a label filter. Node Exporter reports filesystem metrics for every mount point on the system. Without the filter, you'd see results for /, /boot, and other mounts. The filter narrows it to just the root filesystem.

  • Switch to the Graph tab (next to the Table tab) to see your last query plotted over time.
  • Try changing the time range using the controls above the graph. Shrink it to 15 minutes or expand to 1 hour to see how metrics change.
  • Re-enter the memory query and switch to the Graph tab to watch memory availability as a line over time.

Experiment freely

PromQL queries are read-only. You cannot break your monitoring stack by running queries. Try typing the start of a metric name and let autocomplete show you what's available.

Graph shows a flat line or no data?

Your monitoring stack has only been running for a few minutes, so the graph may look flat or sparse. Give it 5-10 minutes of collection time and check again.

If the graph shows "No data", make sure you're on the Graph tab and that the time range isn't set to a period before your stack started.

Help me understand why my Prometheus graph is empty.

You can now query real-time metrics from your EC2 instance using PromQL. Next up, you'll connect Prometheus to Grafana and turn these queries into a polished, auto-refreshing dashboard.

Build a Custom Grafana Dashboard

In the last step, you explored Prometheus directly and wrote PromQL queries to pull real metrics from your server.

Raw query output is powerful for debugging, but production teams need visual dashboards that show server health at a glance. In this step, you will connect Grafana to Prometheus and build a live dashboard that graphs CPU, memory, and disk usage in real time.

In this step, get ready to:
  • Connect Prometheus as a Grafana data source.
  • Build a custom dashboard with CPU, memory, and disk panels.
  • Import a professional pre-built dashboard.
Connect Prometheus as a data source

Before Grafana can display any metrics, it needs to know where to find them. You will add Prometheus as a data source so Grafana can query it for time-series data.

  • Open Grafana in your browser by navigating to http://your EC2 public IP:3000
  • Log in with the default credentials: username admin and password admin.
  • Grafana will prompt you to change your password. Enter a new password and click Submit.
  • In the left-side menu, click Connections.
  • Click Data sources.
  • Click Add new data source.
  • Search for and select Prometheus from the list.
  • In the URL field, enter:
http://prometheus:9090
  • Scroll to the bottom and click Save & test.

You should see a green banner confirming the data source is working.

Why is the URL prometheus:9090 and not localhost?

Grafana runs inside a Docker container on the same network as Prometheus. Inside that network, containers refer to each other by their service name from docker-compose.yml.

Using localhost would point Grafana to itself, not to Prometheus. The service name prometheus resolves to the correct container on the monitoring bridge network.

Save & test failed?

  • Double-check that you typed http://prometheus:9090 exactly. A trailing slash or typo will break the connection.
  • Make sure all three containers are still running. Switch to your SSH terminal and run docker compose ps to verify.
  • Help me debug my Grafana data source connection.
Create a custom dashboard with panels

Now that Grafana can reach Prometheus, you will create a dashboard with three panels showing your server's core health metrics.

  • Click Dashboards in the main menu.
  • Click New and select New Dashboard.
  • Click the Add new element icon (blue plus sign) and click a panel onto the dashboard.
  • On the new panel, click Configure visualization.

The panel editor opens with Prometheus preselected as the data source.

  • In the query editor at the bottom, switch to the Code view (if not already active) and paste the following PromQL query:
100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
  • Click Refresh to run the query. You should see a time-series line appear in the preview.
  • In the Title field in the panel sidebar, enter CPU Usage.
  • Leave the visualization type as Time series (the default for this query shape).
  • Click Save in the top-right corner of the panel editor.
  • Enter Server Monitoring as the dashboard title and click Save.

What does this query do?

This query calculates CPU usage as a percentage. It takes the rate of idle CPU seconds over 5 minutes, multiplies by 100 to get a percentage, then subtracts from 100 to flip it from "idle" to "busy".

Now add a second panel for memory usage.

  • Click Back to dashboard to return to the dashboard view.
  • Click the Add new element icon (blue plus sign) and click another panel onto the dashboard.
  • Click Configure visualization on the new panel.
  • In the query editor, switch to Code view and paste:
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
  • Click Refresh to see the graph.
  • Set the Title to Memory Usage.
  • Leave the visualization type as Time series.
  • Click Save.

What does this query do?

This divides the available memory by the total memory to find what fraction is free, subtracts from 1 to get what fraction is used, then multiplies by 100 for a percentage.

Finally, add a disk usage panel with a gauge visualization.

  • Click Back to dashboard to return to the dashboard view.
  • Click the Add new element icon and click a third panel onto the dashboard.
  • Click Configure visualization on the new panel.
  • In the query editor, switch to Code view and paste:
(1 - node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100
  • Click Refresh to see the result.
  • Set the Title to Disk Usage.
  • For the visualization type, click All visualizations in the panel editor sidebar and select Gauge.
  • Click Save.

Why a gauge for disk usage?

Disk usage changes slowly compared to CPU or memory. A gauge gives you an instant "how full is it" reading rather than a line graph that looks nearly flat.

You should now see all three panels on your Server Monitoring dashboard showing live data from your EC2 instance.

Panels showing No Data?

  • Make sure the Prometheus data source is selected in the panel's Queries tab. Click the data source dropdown and choose your Prometheus source.
  • Check the time range in the top-right corner of the dashboard. Set it to Last 15 minutes so recent scrapes are included.
  • Verify Node Exporter is still reporting. Open http://your EC2 public IP:9090/targets and confirm the "node" target shows UP.
  • Help me fix my Grafana panels that show No Data.
Import the Node Exporter Full dashboard

Building panels one at a time is a great way to learn, but the community has already built comprehensive dashboards. The Node Exporter Full dashboard (ID 1860) is the most popular community dashboard on the Grafana marketplace and graphs nearly all default metrics from Node Exporter.

  • Click Dashboards in the main menu.
  • Click New and select Import dashboard from the drop-down menu.
  • In the ID field, enter 1860 and click Load.
  • In the Prometheus data source dropdown at the bottom, select the Prometheus data source you configured earlier.
  • Click Import.

The dashboard loads with dozens of panels showing CPU, memory, disk, network, and system information. All graphs auto-refresh with live data from your EC2 instance.

What is dashboard ID 1860?

Grafana maintains a public dashboard marketplace at grafana.com where community members share pre-built dashboards. Each dashboard has a numeric ID you can use to import it directly.

Dashboard 1860 (Node Exporter Full) is designed to work with the default node job name, which matches the job name in your prometheus.yml configuration.

Imported dashboard showing empty panels?

  • Make sure you selected the correct Prometheus data source during import. If no data source appeared, go back to Connections and confirm the Prometheus data source exists and passes the Save & test check.
  • The dashboard expects the job name node in your prometheus.yml. Verify your scrape config uses job_name: "node" (not a different name).
  • Help me fix my imported Node Exporter Full dashboard.

Your monitoring stack is now complete. You have raw metrics in Prometheus and polished visualizations in Grafana. Next up, the Secret Mission challenges you to add alerting rules that fire when something goes wrong.

Secret mission

Add Alerting Rules

Your monitoring stack collects metrics and displays them on dashboards. But dashboards only help if someone is watching. In production, teams configure alerting rules that automatically detect problems and notify engineers. In this secret mission, you will write a Prometheus alerting rule that fires when CPU usage exceeds 80% for 2 minutes, then stress-test your server to watch the alert trigger in real time.

Clean Up Your Resources

Clean Up Your Resources

Your EC2 instance (t3.small) costs approximately $0.02/hour while running. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Cost warning

The EC2 instance you launched costs approximately $0.50/day while running. If you are not actively using it, stop or terminate it to avoid unexpected charges on your AWS bill.

Resources you used:

  • EC2 instance (prometheus-grafana-lab, t3.small) with Docker and the monitoring stack installed.
  • Key pair file (.pem file) saved on your local machine.

Keep everything running

No action needed. Choose this if you are still actively building or experimenting with your monitoring stack.

  • Your EC2 instance will continue running and incurring charges (~$0.50/day).
  • You can SSH back in at any time and access Prometheus at port 9090 and Grafana at port 3000.
  • Docker containers will restart automatically thanks to the restart: unless-stopped policy in your Compose file.

Pause - I'll come back to this later

Stop the EC2 instance to pause compute charges. Your data persists on the EBS volume. You only pay for storage while stopped.

  • In the AWS Console, navigate to the EC2 service.
  • In the navigation pane, click Instances.
  • Select your prometheus-grafana-lab instance.
  • Click Instance state, then Stop instance.
  • Click Stop when prompted for confirmation.
  • When you are ready to resume, select the instance again and choose Instance state, then Start instance. Your public IP will change, so use the new IP to SSH in.

Delete - I don't want to use this again

Remove all project resources permanently. This deletes the instance and all data on it. You will no longer be charged.

  • In the AWS Console, navigate to the EC2 service.
  • In the navigation pane, click Instances.
  • Select your prometheus-grafana-lab instance.
  • Click Instance state, then Terminate (delete) instance.
  • Click Terminate (delete) when prompted for confirmation.
  • The instance will briefly show a state of terminated before disappearing from the console.
  • Delete the .pem key pair file from your local machine. It is no longer needed.

That's a Wrap!

That's a Wrap!

Nice work! You just built a complete server monitoring system from scratch on AWS. You now have hands-on experience with the same observability tools that production infrastructure teams rely on every day.

You learned how to:

  • Deploy a 3-service monitoring stack (Prometheus, Grafana, Node Exporter) on an EC2 instance using Docker Compose.
  • Write PromQL queries to calculate real-time CPU usage, memory availability, and disk space from time-series metrics.
  • Build a custom Grafana dashboard with live-updating panels and import the professional Node Exporter Full dashboard (ID 1860).
  • Secret Mission: Configure Prometheus alerting rules that fire when CPU usage crosses a threshold.

Ready to quiz yourself?