Developer Tools AWS

Dashy 4.9.0 on AWS User Guide

| Product: Dashy 4.9.0 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Dashy 4.9.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg developer and API tools listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

Dashy is an open source, self-hosted dashboard and start page. It puts the links to every application, service and bookmark you use on one page, grouped into sections with icons, themes, keyboard search, widgets and live status checks that show at a glance which services are up. The layout lives in one YAML file that you can edit on the server or from Dashy's built in editor.

Upstream Dashy has no login of its own by default (its optional login is enforced in the browser, not on the server), so anyone who can reach it can read and change the dashboard. The cloudimg image makes it a private dashboard for your team:

  • HTTPS from the first boot. nginx serves Dashy on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
  • A password unique to your instance, on every path. HTTP Basic Auth is required for the user admin on every page, asset, configuration file (/conf.yml in any spelling or encoding) and server endpoint (config saving, status checks, the CORS proxy). The password is generated on first boot and written to /var/lib/cloudimg/dashy-credentials.txt (root only); nginx stores only a bcrypt hash of it. No password is baked into the image.
  • Status checks and the CORS proxy cannot reach the metadata service. Dashy's server fetches URLs for its status checks, ping checks and CORS proxy. The Dashy service and nginx are blocked from the EC2 instance metadata service at the kernel level, and these fetch endpoints only answer requests from Dashy's own pages (a request another web site makes through your browser is refused).
  • Fail closed. Dashy and nginx refuse to start until first boot has generated the certificate and the password.
  • Hardened defaults. Dashy's Node.js server listens on 127.0.0.1 only and runs as an unprivileged dashy user that can write nothing but its configuration directory. Requests for any host name the instance does not serve are dropped, cross site requests that change state are refused, and the AMI requires IMDSv2.
  • Patched dependencies, no build tools on the image. Dashy is built from the pinned 4.9.0 release with its two vulnerable JavaScript dependencies (body-parser, fast-uri) raised to fixed versions in the same major versions; the shipped packages have no known npm advisories at build time. The Node.js 24 LTS runtime ships without npm, yarn or a compiler.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: Dashy 4.9.0 (/opt/dashy), Node.js 24.22.1 LTS (/opt/node), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 22 (SSH), TCP 443 (Dashy) and TCP 80 (redirects to HTTPS) from your own networks only.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Dashy 4.9.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (Dashy itself needs little; a smaller instance works for a small team), your key pair, and a security group as above. To run status checks against services in your VPC, launch Dashy in a subnet that can reach them.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=dashy}]'

Step 2: Connect to your instance

Version SSH user
Dashy 4.9.0 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot generates the certificate and the password, then Dashy and nginx start:

systemctl is-active dashy nginx dashy-firstboot
sudo test -f /var/lib/cloudimg/dashy-firstboot.done && echo FIRSTBOOT_DONE

All three units are active and the first boot marker is present:

active
active
active
FIRSTBOOT_DONE

Only SSH and nginx listen on the network; Dashy's Node.js server (MainThread) listens on 127.0.0.1 only:

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | grep -v '127.0.0.5' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
127.0.0.1:4000 users:(("MainThread"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))

Step 4: Get your URL and password

sudo cat /var/lib/cloudimg/dashy-credentials.txt

The file is readable by root only and holds the URL, the user name and this instance's password:

DASHY_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
DASHY_USERNAME=admin
DASHY_PASSWORD=<DASHY_PASSWORD>

The URL uses the instance's public DNS name from EC2 instance metadata. The certificate also names the public and private IP addresses, so https://<public-ip>/ works too. To serve Dashy under your own DNS name, see Step 9.

Step 5: Sign in from your browser

Open the DASHY_URL in your browser. The certificate is self signed, so your browser asks you to confirm it the first time (or install your own certificate, Step 9). Sign in as admin with the password from Step 4; the browser remembers the login for the session.

The image starts with a small cloudimg dashboard: a Getting Started section with links to this guide and the Dashy documentation, and a Your Services section to replace with your own:

The cloudimg Dashy start page with the Getting Started and Your Services sections

Step 6: Edit the dashboard in the browser

Open the options panel with the wrench icon at the top right, then choose Config. The configuration menu shows the version that is running and where the configuration comes from (/conf.yml):

The Dashy configuration menu with View / Export Config, Edit Config, Change App Language, Edit Custom CSS, Enable Cloud Sync, Reset Local Settings, Debug Info and About, running Dashy version 4.9.0

Edit Config opens the YAML editor with validation. Save to Disk writes the file on the server (/opt/dashy/user-data/conf.yml) for every user, keeping a backup of the previous version in /opt/dashy/user-data/config-backups/; Save Locally keeps the change in your own browser only:

The Dashy Edit Config tab showing the conf.yml YAML with Format, Reset, Preview Changes, Save Locally and Save to Disk

Saving to disk accepts only .yml / .yaml file names and writes only inside /opt/dashy/user-data; the rest of the server is read only for the Dashy service. Enable Cloud Sync is optional and off: it would upload your configuration to Dashy's own sync service, so leave it off unless you want that.

Step 7: Add your services and status checks from the command line

The dashboard is one YAML file. This replaces it with two sections and turns on status checks, which make Dashy's server request each item's URL and show a green or red dot. Dashy picks the file up on the next page load:

sudo tee /opt/dashy/user-data/conf.yml >/dev/null <<'YAML'
pageInfo:
  title: My Services
  description: Everything we run, on one page
appConfig:
  theme: colorful
  layout: auto
  statusCheck: true
  statusCheckInterval: 300
sections:
  - name: Websites
    icon: fas fa-globe
    items:
      - title: cloudimg
        description: cloudimg website
        icon: fas fa-cloud
        url: https://www.cloudimg.co.uk/
      - title: GitHub
        description: Code hosting
        icon: fab fa-github
        url: https://github.com/
  - name: This server
    icon: fas fa-server
    items:
      - title: Dashy health
        description: The Dashy server on this instance
        icon: fas fa-heartbeat
        url: http://127.0.0.1:4000/healthz
YAML
sudo chown dashy:dashy /opt/dashy/user-data/conf.yml
sudo chmod 0640 /opt/dashy/user-data/conf.yml
sudo grep -c 'title:' /opt/dashy/user-data/conf.yml
4

Check that Dashy serves the new configuration and that a status check works (the password is read from the credentials file and passed to curl on standard input, so it never appears in the process list):

sudo bash -c '
  F=/var/lib/cloudimg/dashy-credentials.txt
  P=$(grep "^DASHY_PASSWORD=" $F | cut -d= -f2-)
  H=$(grep "^DASHY_URL=" $F | sed -E "s#^DASHY_URL=https://([^/]+)/.*#\1#")
  auth() { printf "user = \"admin:%s\"\n" "$P"; }
  auth | curl -K - -sk --resolve "$H:443:127.0.0.1" "https://$H/conf.yml" | grep -E "title: (My Services|cloudimg|GitHub)"
  auth | curl -K - -sk --resolve "$H:443:127.0.0.1" -H "Sec-Fetch-Site: same-origin" \
    "https://$H/status-check?url=https%3A%2F%2Fgithub.com%2F" | grep -o "\"successStatus\":true"
'
  title: My Services
      - title: cloudimg
      - title: GitHub
"successStatus":true

Reload the page in your browser. Each item now shows its status dot:

The My Services dashboard with the cloudimg, GitHub and Dashy health items, each with a green status dot

Status checks and the CORS proxy run from the instance, so they reach what the instance can reach: hosts in your VPC through its security groups and routes, and the internet through its route table. The instance metadata service (169.254.169.254) is never reachable from them.

Step 8: Check the security posture from the command line

Without a login Dashy is refused, including its configuration file under an encoded name; port 80 redirects to HTTPS; the health check stays open for load balancers; and a request for a host name the instance does not serve is dropped without an answer:

H=$(sudo grep '^DASHY_URL=' /var/lib/cloudimg/dashy-credentials.txt | sed -E 's#^DASHY_URL=https://([^/]+)/.*#\1#')
curl -s -o /dev/null -w 'http://  -> %{http_code} %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -o /dev/null -w 'no login -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/"
curl -sk -o /dev/null -w 'conf.yml, no login -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/conf.yml"
curl -sk -o /dev/null -w 'conf.yml encoded, no login -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/%63onf.yml"
curl -sk -o /dev/null -w 'health -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/health"
curl -sk -o /dev/null -w 'unknown host -> %{http_code}\n' --resolve "evil.example:443:127.0.0.1" "https://evil.example/" || true
http://  -> 301 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
no login -> HTTP 401
conf.yml, no login -> HTTP 401
conf.yml encoded, no login -> HTTP 401
health -> HTTP 200
unknown host -> 000

000 means nginx closed the connection without answering, which is how it treats any host name it does not serve.

With the password, Dashy is served. Its status check endpoint answers Dashy's own pages but refuses a request that a browser marks as coming from another site or that names another site as its origin (HTTP 403), so another web page cannot use your signed in browser to make the server fetch things. A status check aimed at the instance metadata service fails:

sudo bash -c '
  F=/var/lib/cloudimg/dashy-credentials.txt
  P=$(grep "^DASHY_PASSWORD=" $F | cut -d= -f2-)
  H=$(grep "^DASHY_URL=" $F | sed -E "s#^DASHY_URL=https://([^/]+)/.*#\1#")
  auth() { printf "user = \"admin:%s\"\n" "$P"; }
  R="--resolve $H:443:127.0.0.1"
  auth | curl -K - -sk $R -o /dev/null -w "with login -> HTTP %{http_code}\n" "https://$H/"
  auth | curl -K - -sk $R "https://$H/" | grep -o "<title>[^<]*</title>"
  auth | curl -K - -sk $R -o /dev/null -w "status-check, same origin -> HTTP %{http_code}\n" \
    -H "Sec-Fetch-Site: same-origin" "https://$H/status-check?url=http%3A%2F%2F127.0.0.1%3A4000%2Fhealthz"
  auth | curl -K - -sk $R -o /dev/null -w "status-check, cross-site -> HTTP %{http_code}\n" \
    -H "Sec-Fetch-Site: cross-site" "https://$H/status-check?url=http%3A%2F%2F127.0.0.1%3A4000%2Fhealthz"
  auth | curl -K - -sk $R -o /dev/null -w "status-check, other Origin -> HTTP %{http_code}\n" \
    -H "Origin: https://evil.example" "https://$H/status-check?url=http%3A%2F%2F127.0.0.1%3A4000%2Fhealthz"
  auth | curl -K - -sk $R -H "Sec-Fetch-Site: same-origin" \
    "https://$H/status-check?url=http%3A%2F%2F169.254.169.254%2Flatest%2Fmeta-data%2F" | grep -o "\"successStatus\":false"
'
with login -> HTTP 200
<title>Dashy</title>
status-check, same origin -> HTTP 200
status-check, cross-site -> HTTP 403
status-check, other Origin -> HTTP 403
"successStatus":false

nginx keeps only a bcrypt hash of the password; the plain text is in the root only credentials file:

sudo sed -E 's/\$2y\$12\$.*/$2y$12$<bcrypt-hash>/' /etc/nginx/.dashy-htpasswd
sudo stat -c '%a %U:%G %n' /etc/nginx/.dashy-htpasswd /var/lib/cloudimg/dashy-credentials.txt
admin:$2y$12$<bcrypt-hash>
640 root:www-data /etc/nginx/.dashy-htpasswd
600 root:root /var/lib/cloudimg/dashy-credentials.txt

The certificate was generated on this instance and names its own addresses:

H=$(sudo grep '^DASHY_URL=' /var/lib/cloudimg/dashy-credentials.txt | sed -E 's#^DASHY_URL=https://([^/]+)/.*#\1#')
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name:
    DNS:ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost

Dashy and nginx cannot reach the instance metadata service; Dashy runs as its own user with only its configuration directory writable; and neither service starts until first boot has finished:

systemctl show dashy nginx -p IPAddressDeny
systemctl show dashy -p User -p ProtectSystem -p ReadWritePaths -p NoNewPrivileges
systemctl cat dashy nginx | grep -E '^ExecStartPre='
IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32

IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32
User=dashy
ReadWritePaths=/opt/dashy/user-data
ProtectSystem=strict
NoNewPrivileges=yes
ExecStartPre=+/usr/bin/test -f /var/lib/cloudimg/dashy-firstboot.done
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStartPre=/usr/bin/test -f /var/lib/cloudimg/dashy-firstboot.done
ExecStartPre=/usr/bin/test -s /etc/nginx/.dashy-htpasswd

Step 9: Your own DNS name and certificate

Point a DNS record at the instance, then set the name Dashy is served as. This mints a new self signed certificate for the name, updates nginx, the cross site protection and the URL in the credentials file, and keeps the name across reboots:

sudo dashy-set-hostname <your-domain>

To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx (running dashy-set-hostname again replaces it with a self signed one):

sudo install -m 0644 <your-domain>.crt /etc/ssl/dashy/dashy.crt
sudo install -m 0600 <your-domain>.key /etc/ssl/dashy/dashy.key
sudo nginx -t && sudo systemctl reload nginx

Step 10: Change the password or add users

The login is an nginx Basic Auth file. To set a new password for admin, hand it to htpasswd on standard input (printf is a shell builtin, so the password never appears in the process list; start the line with a space so most shells keep it out of your history), then reload nginx:

 printf '%s\n' '<new-password>' | sudo htpasswd -i -B -C 12 /etc/nginx/.dashy-htpasswd admin
sudo systemctl reload nginx

Running sudo htpasswd -B -C 12 /etc/nginx/.dashy-htpasswd <user> without -i prompts for the password instead. Use it with another user name to give a colleague their own login, and sudo htpasswd -D /etc/nginx/.dashy-htpasswd <user> to remove one. Then update or delete DASHY_PASSWORD in /var/lib/cloudimg/dashy-credentials.txt so it does not hold an old password.

nginx checks the login and does not pass it on to Dashy, so Dashy's own optional login (appConfig.auth) is not needed; leave it unset.

Step 11: Server components

Component Path
Dashy (built app, server, production packages, licence) /opt/dashy
Dashboard configuration (and its backups) /opt/dashy/user-data/
Node.js 24 LTS runtime (no npm, no yarn) /opt/node
Build record, dependency audit, default configuration /usr/share/doc/cloudimg-dashy/
Dashy service dashy.service (user dashy, 127.0.0.1:4000)
nginx site /etc/nginx/sites-available/cloudimg-dashy
Basic Auth file /etc/nginx/.dashy-htpasswd (bcrypt)
Certificate and key /etc/ssl/dashy/
First boot script /usr/local/sbin/dashy-firstboot.sh
Host name helper /usr/local/sbin/dashy-set-hostname
Credentials /var/lib/cloudimg/dashy-credentials.txt (mode 0600)
cat /opt/dashy/VERSION
/opt/node/bin/node --version
ls /opt/node/bin
command -v npm yarn || echo "no npm or yarn"
nginx -v 2>&1
dashy_version=4.9.0
dashy_commit=93a33d945c8b84e5dd37c8bef103770d4701d175
node_version=v24.22.1
v24.22.1
node
no npm or yarn
nginx version: nginx/1.24.0 (Ubuntu)

/usr/share/doc/cloudimg-dashy/BUILD-INFO.txt records the exact source, Node.js and dependency versions, and npm-audit-production.json the npm advisory audit of every shipped package (0 advisories at build time). To return to the starting dashboard, copy /usr/share/doc/cloudimg-dashy/conf.yml.default over /opt/dashy/user-data/conf.yml. When Dashy starts it checks GitHub for a newer Dashy release and only notes the result in its log (journalctl -u dashy). The dashboard's Font Awesome icons are loaded by the browser from Font Awesome's CDN, as upstream does.

Step 12: Security recommendations

  • Allow ports 22, 80 and 443 in the security group from your own networks only.
  • Keep the per instance password secret, and rotate it if it may have been shared (Step 10).
  • Use your own DNS name and a certificate your clients trust (Step 9).
  • Status checks and the CORS proxy can reach whatever the instance can reach in your VPC; give the instance a security group and subnet that match what you want the dashboard to see.
  • Keep the operating system patched; unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else.

Support and licensing

Dashy is open source under the MIT licence (/opt/dashy/LICENSE); there is no per user or per CPU licence fee. The AMI price covers cloudimg's hardening, first boot setup and support.

  • Email: support@cloudimg.co.uk
  • Website: https://www.cloudimg.co.uk
  • Support: 24/7, with a 24 hour response SLA