Observability AWS

Perses Dashboards as Code Observability Platform on AWS User Guide

| Product: Perses

Overview

Perses is an open source dashboard and observability visualization platform, and a Cloud Native Computing Foundation project. Like Grafana, it renders dashboards over your observability data, with native support for Prometheus metrics, Tempo traces, Loki logs and Pyroscope profiles. What sets Perses apart is its dashboards as code approach: dashboards follow an open, standardized specification and can be authored, validated and version controlled as code with its CLI and SDKs, which makes it a natural fit for GitOps workflows.

Perses ships no authentication in this deployment shape, so the cloudimg image does not leave it open. The server is bound to the loopback interface only and nginx fronts it on port 80 behind an HTTP Basic Auth gate whose password is generated uniquely and at random on the first boot of every instance. Nothing usable ships inside the image.

What is included:

  • Perses 0.53.1 from the official upstream release (Apache License 2.0, perses/perses), pinned and sha256 verified at build time
  • The server binary, the percli command line tool, and 24 bundled panel and query plugin modules under /opt/perses
  • Perses bound to 127.0.0.1:8080, fronted by nginx on :80 with X-Forwarded-* headers
  • A file based dashboard store at /opt/perses/data, so there is no external database to run
  • An unauthenticated /healthz endpoint for load balancer and probe checks
  • perses-firstboot.service generating a 24 character per instance Basic Auth password (bcrypt in /etc/nginx/.perses.htpasswd) plus a per instance at-rest encryption key, and writing /root/perses-credentials.txt (mode 0600 root:root)
  • A seeded demo project and welcome dashboard so the interface is populated on first boot
  • 24/7 cloudimg support

Prerequisites

An AWS account, an EC2 key pair, and a VPC subnet. The instance security group should allow inbound TCP 22 (SSH) and TCP 80 (web interface) from your trusted CIDR. m5.large is a comfortable default; Perses is a single lightweight Go service with a file based store, so smaller instances are viable for light use and larger ones only matter once you are driving many concurrent dashboard queries against your datasources.

Connecting to your instance

SSH in as the default login user for the operating system variant you launched. This listing currently ships one variant:

OS variant SSH login user
Ubuntu 24.04 LTS ubuntu
ssh ubuntu@<public-ip>

Step 1: Launch from AWS Marketplace

Subscribe to the cloudimg Perses listing in AWS Marketplace and launch an instance. Pick the m5.large size, choose your VPC and subnet, and attach a security group that allows inbound TCP 22 and TCP 80 from your trusted CIDR.

Step 2: SSH In

ssh ubuntu@<public-ip>

Step 3: Service Status

Confirm both the Perses server and the nginx reverse proxy are active. Perses is a single Go binary and starts in a second or two.

sudo systemctl is-active perses nginx
active
active

Step 4: Confirm the Health Endpoint

nginx serves a static, unauthenticated health endpoint. This is what you point an Application Load Balancer target group or an external uptime check at, so probes never need the instance password.

curl -s -o /dev/null -w 'healthz HTTP %{http_code}\n' -m 10 http://127.0.0.1/healthz
healthz HTTP 200

Step 5: Confirm Secure By Default Authentication

Everything except /healthz sits behind the per instance Basic Auth gate. An unauthenticated request to the API must be rejected.

curl -s -o /dev/null -w 'unauthenticated API HTTP %{http_code}\n' -m 10 http://127.0.0.1/api/v1/projects
unauthenticated API HTTP 401

Confirm the isolation as well: Perses itself listens only on loopback, and nginx is the only process bound to a public address.

sudo ss -tlnp | grep -E ':8080|:80 ' | awk '{print $1, $4, $6}'
LISTEN 0.0.0.0:80 users:(("nginx",pid=2223,fd=5),("nginx",pid=2222,fd=5),("nginx",pid=2221,fd=5))
LISTEN 127.0.0.1:8080 users:(("perses",pid=2214,fd=3))
LISTEN [::]:80 users:(("nginx",pid=2223,fd=6),("nginx",pid=2222,fd=6),("nginx",pid=2221,fd=6))

127.0.0.1:8080 means the Perses API cannot be reached from the network except through the authenticated proxy.

Step 6: Read Per-Instance Credentials

The first boot service generated this instance's web password and wrote the credentials file. Read it now and keep the terminal open while you sign in.

sudo cat /root/perses-credentials.txt

Pick up PERSES_URL, PERSES_USERNAME (always admin) and PERSES_PASSWORD. These values are unique to this instance and exist nowhere in the image; they are generated on the first boot of every instance you launch.

You can confirm the first boot ran cleanly:

sudo journalctl -u perses-firstboot.service --no-pager -o cat | tail -3
Starting perses-firstboot.service - cloudimg Perses first-boot (per-instance Basic Auth password + encryption key + URL/MOTD)...
[2026-07-25T15:10:40+0000] perses-firstboot: First-boot prep complete; systemd will start perses + nginx. UI at http://13.221.114.30/
Finished perses-firstboot.service - cloudimg Perses first-boot (per-instance Basic Auth password + encryption key + URL/MOTD).

Step 7: Sign In to Perses

Browse to http://<public-ip>/. Your browser prompts for a username and password, which is proof the platform is not open to the internet. Enter admin and the PERSES_PASSWORD value from the credentials file.

The home view lists your projects. The seeded demo project is already there with one dashboard, so the interface is populated rather than empty on a fresh launch.

The Perses home view titled Welcome to Perses, with Create Project, Create Dashboard and Import Dashboard actions, a Projects list containing the seeded DEMO project showing 1 dashboard, and a Recently Viewed Dashboards panel

Step 8: Open the Seeded Dashboard

Click into DEMO and open the Welcome dashboard. It uses Markdown panels, which render standalone with no datasource attached, so the dashboard is meaningful before you have connected anything to it.

The seeded Welcome dashboard in the demo project, showing an Overview section with a Welcome to Perses markdown panel and a Getting started panel listing next steps, with the time range picker set to Last 1 hour

Step 9: Explore the Project

The project view lists that project's dashboards with creation and update times, plus tabs for Variables, Datasources and Secrets scoped to the project. This is where you add the Prometheus, Tempo, Loki or Pyroscope endpoint your dashboards will query.

The demo project view with a Dashboards tab listing the Welcome dashboard with creation and update times and edit, duplicate and delete actions, alongside tabs for Variables, Datasources and Secrets

Step 10: Confirm the API Round-Trip

The same REST API drives the interface, the CLI and any automation you write. Read the projects and dashboards back with the per instance password.

PASS=$(sudo grep '^PERSES_PASSWORD=' /root/perses-credentials.txt | cut -d= -f2-)
curl -s -u "admin:${PASS}" -m 15 http://127.0.0.1/api/v1/projects | python3 -c "import sys,json;d=json.load(sys.stdin);print('projects:',len(d));[print(' -',p['metadata']['name']) for p in d]"
projects: 1
 - demo
PASS=$(sudo grep '^PERSES_PASSWORD=' /root/perses-credentials.txt | cut -d= -f2-)
curl -s -u "admin:${PASS}" -m 15 http://127.0.0.1/api/v1/projects/demo/dashboards | python3 -c "import sys,json;d=json.load(sys.stdin);print('dashboards in demo:',len(d));[print(' -',x['metadata']['name'],'|',x['spec'].get('display',{}).get('name','')) for x in d]"
dashboards in demo: 1
 - welcome | Welcome

Step 11: Create Your Own Project

Projects are how Perses scopes dashboards, datasources and secrets, typically one per team or per system. Create one over the API (the Create Project button in the interface does the same thing).

PASS=$(sudo grep '^PERSES_PASSWORD=' /root/perses-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'create project HTTP %{http_code}\n' -m 15 -u "admin:${PASS}" \
  -H 'Content-Type: application/json' -X POST http://127.0.0.1/api/v1/projects \
  -d '{"kind":"Project","metadata":{"name":"my-team"}}'
curl -s -u "admin:${PASS}" -m 15 http://127.0.0.1/api/v1/projects | python3 -c "import sys,json;d=json.load(sys.stdin);print('projects now:',[p['metadata']['name'] for p in d])"
curl -s -o /dev/null -w 'cleanup delete HTTP %{http_code}\n' -m 15 -u "admin:${PASS}" -X DELETE http://127.0.0.1/api/v1/projects/my-team
create project HTTP 200
projects now: ['demo', 'my-team']
cleanup delete HTTP 204

The last line removes the throwaway project again. Drop it when you are creating a project you intend to keep.

Step 12: Dashboards as Code with percli

The bundled percli command line tool is what makes the dashboards as code workflow real: dashboard definitions live in your Git repository, and the CLI applies them to a Perses instance from CI.

/opt/perses/percli version
client:
    buildTime: "2026-03-12"
    version: 0.53.1
    commit: 2b65a98ba3febd6342d5d99c1b498c6ff7ae08f2

From a workstation, log in to this instance and apply a dashboard definition from a file. Basic Auth credentials go on the URL for the CLI, and the file is the same JSON the API accepts:

percli login http://admin:<password>@<public-ip>
percli apply -f my-dashboard.json --project my-team

Keep those definitions in version control and run percli apply from your pipeline, and dashboard changes get reviewed like any other change.

Step 13: Review the Server Configuration

The Config view in the top navigation renders the running server configuration and the installed plugin set. Note enable_auth: false (the nginx gate is the single credential surface), the file based store at /opt/perses/data, and the at-rest encryption_key shown only as <secret>.

The Perses Configuration view showing the Server Configuration tab with the running JSON configuration, including a redacted encryption_key, enable_auth false, and the file database folder set to /opt/perses/data, plus an Installed Plugins tab

You can confirm the plugin set over the API. All 24 bundled panel and query modules, including Markdown, TimeSeriesChart, StatChart and the Prometheus datasource, are extracted and loaded at startup.

PASS=$(sudo grep '^PERSES_PASSWORD=' /root/perses-credentials.txt | cut -d= -f2-)
curl -s -u "admin:${PASS}" -m 15 http://127.0.0.1/api/v1/plugins | python3 -c "import sys,json;d=json.load(sys.stdin);print('plugin modules loaded:',len(d))"
plugin modules loaded: 24

Step 14: Run the Built-In Self-Test

The image ships a diagnostic that proves the whole stack end to end: the health endpoint, the authentication gate (unauthenticated and wrong password both rejected), the seeded demo resources, and a full create, read and delete round-trip through the proxy. Run it any time you want to confirm the instance is healthy.

PASS=$(sudo grep '^PERSES_PASSWORD=' /root/perses-credentials.txt | cut -d= -f2-)
sudo /usr/local/sbin/perses-selftest.sh "$PASS"
OK perses gate + REST round-trip + demo resources verified

Step 15: Connect Your First Datasource

Perses queries your existing observability backends; it does not store metrics itself. In the interface, open your project, go to the Datasources tab, and add a datasource pointing at your Prometheus, Tempo, Loki or Pyroscope endpoint. Then create a dashboard with a TimeSeriesChart panel whose query targets it.

Two things worth knowing:

  1. Reachability. The datasource URL is resolved from this instance, so the security group and routing between this instance and your metrics backend must allow it.
  2. Secrets. Credentials for a datasource go in the project's Secrets tab. They are encrypted at rest with the per instance encryption key generated at first boot, which is why that key never ships inside the image.

Step 16: Production Hardening Checklist

Before exposing Perses beyond a trusted network, work through these:

  1. TLS termination. Add a certificate and redirect port 80 to 443, either on an Application Load Balancer in front of the instance or on the instance itself. Basic Auth over plain HTTP sends the password reversibly encoded, so terminate TLS before this is reachable over an untrusted network.
  2. Restrict the security group. Limit inbound TCP 80 and 22 to your office or VPN CIDR rather than leaving them open to the world.
  3. Rotate or add web users. The gate is a standard bcrypt htpasswd file. Add users with sudo htpasswd -B /etc/nginx/.perses.htpasswd <user> and reload nginx; rotate the admin entry the same way, then update your records.
  4. Back up /opt/perses/data. That directory is the entire dashboard store: projects, dashboards, datasources and encrypted secrets. Snapshot the volume or copy the directory on a schedule.
  5. Protect the encryption key. /etc/perses/perses.env holds the at-rest key for datasource secrets. If you restore /opt/perses/data onto a different instance, carry that key across or the stored secrets will not decrypt.
  6. Keep it patched. Unattended upgrades is enabled for OS packages. Perses itself is a pinned upstream release under /opt/perses; review the upstream release notes before replacing the binary.

Architecture

Component Path Notes
Perses server perses.service Go server with the React interface embedded, bound to 127.0.0.1:8080, runs as the non-login perses user
Server config /etc/perses/config.yaml File database backend, enable_auth: false (nginx is the gate)
Dashboard store /opt/perses/data Projects, dashboards, datasources and encrypted secrets as JSON
Plugin modules /opt/perses/plugins-archive, /opt/perses/plugins 24 bundled panel and query modules, extracted at startup
CLI /opt/perses/percli Dashboards as code client
nginx vhost /etc/nginx/sites-available/cloudimg-perses Reverse proxy on :80, Basic Auth gate, unauthenticated /healthz
Basic Auth file /etc/nginx/.perses.htpasswd bcrypt admin entry, generated per instance at first boot (mode 0640 root:www-data)
Encryption key /etc/perses/perses.env Per instance at-rest key for datasource secrets (mode 0640)
Customer creds /root/perses-credentials.txt Mode 0600 root:root; PERSES_URL, username, password
Self-test /usr/local/sbin/perses-selftest.sh Proves gate, demo resources and REST round-trip
Firstboot script /usr/local/sbin/perses-firstboot.sh Generates the per instance password and encryption key, writes the credentials file
Firstboot sentinel /var/lib/cloudimg/perses-firstboot.done Created when first boot completes

Support

cloudimg provides 24/7 support for this image. Open a ticket at https://www.cloudimg.co.uk/support/ with your instance ID and a brief description of the issue. Common asks: connecting a first datasource, moving dashboards into version control with percli, adding TLS and a domain, and backing up or migrating the dashboard store.