App Management
The following article is a primer on managing self-hosted apps. It covers everything from keeping the Dashy (or any other app) up-to-date, secure, backed up, to other topics like auto-starting, monitoring, log management, web server configuration and using custom domains.
Contentsβ
- Providing Assets
- File Ownership and Permissions
- Running Commands
- Healthchecks
- Logs and Performance
- Auto-Starting at Boot
- Updating
- Backing Up
- Scheduling
- SSL Certificates
- Authentication
- Network Exposure
- Managing with Compose
- Environmental Variables
- Setting Headers
- Remote Access
- Custom Domain
- Verifying Releases
- Securing Containers
- Web Server Configuration
- Running a Modified App
- Building your Own Container
Providing Assetsβ
Dashy reads everything for your dashboard from a single host directory mounted into the container at /app/user-data. This is done with a Docker volume, e.g. -v /path/to/your/user-data:/app/user-data.
The directory must contain a conf.yml. It can also contain anything else you want served from the web root: sub-config files for additional pages, item icons, favicon, fonts, custom CSS, manifest, and so on. Any file placed there is reachable at /<filename> in the browser, overriding files of the same name in the bundled public/ defaults.
Typical contents:
conf.yml- Main config (required)*.yml/*.yaml- Sub-config files for multi-page supportitem-icons/- Local icons referenced byicon: foo.pngfavicon.ico,manifest.webmanifest,robots.txt- Override the bundled defaultsfonts/,widget-resources/- Custom fonts or assets used by widgets
File Ownership and Permissionsβ
Inside the container, Dashy runs as a non-root user with uid/gid 1000 (the built-in node user from the Node base image). This is fine for the vast majority of installs, since the first user on a default Linux or macOS box is also uid 1000, so a bind-mounted user-data directory is read/writable straight away.
It only gets fiddly if your host uid happens to be something else (NAS systems and multi-user servers being the usual culprits). In that case, config saves from the UI will fail with a permission error, because the container's uid 1000 doesn't own your directory.
There are two ways to sort it. Pick whichever is less hassle:
-
Run the container as your own user. The cleanest option, since you don't touch host file ownership.
On
docker run:docker run -d -p 8080:8080 \--user $(id -u):$(id -g) \-v /path/to/user-data:/app/user-data \lissy93/dashy:latestIn compose, uncomment the
user:line under the service and set it:user: "1001:1001" # whatever `id -u` and `id -g` give you -
Hand the directory to uid 1000. Quicker if you don't mind changing host ownership:
sudo chown -R 1000:1000 /path/to/user-data
If you're on rootless Podman, use --userns=keep-id instead of --user. Rootless Podman maps container uids to a sub-uid range, so without it a directory you own looks root-owned from inside the container.
Note that if you run the container as root (e.g. --user 0:0), Dashy will still work, but you lose the security benefit of a non-root container. Don't do that unless you've a good reason.
Running Commandsβ
If you're running an app in Docker, then commands will need to be passed to the container to be executed. This can be done by preceding each command with docker exec -it [container-id], where container ID can be found by running docker ps. For example docker exec -it 26c156c467b4 yarn validate-config. You can also enter the container, with docker exec -it [container-id] /bin/ash, and navigate around it with normal Linux commands.
Dashy has several commands that can be used for various tasks, you can find a list of these either in the Developing Docs, or by looking at the package.json. These can be used by running yarn [command-name].
Healthchecksβ
Healthchecks are configured to periodically check that Dashy is up and running correctly on the specified port. By default, the health script is called every 5 minutes, but this can be modified with the --health-interval option. You can check the current container health with: docker inspect --format "{{json .State.Health }}" [container-id], and a summary of health status will show up under docker ps. You can also manually request the current application status by running docker exec -it [container-id] yarn health-check. You can disable healthchecks altogether by adding the --no-healthcheck flag to your Docker run command.
To restart unhealthy containers automatically, check out Autoheal. This image watches for unhealthy containers, and automatically triggers a restart. (This is a stand in for Docker's --exit-on-unhealthy that was proposed, but not merged). There's also Deunhealth, which is super light-weight, and doesn't require network access. Both need access to the Docker socket, see Don't Expose the Docker Daemon Socket for the trade-off.
docker run -d \
--name autoheal \
--restart=always \
-e AUTOHEAL_CONTAINER_LABEL=all \
-v /var/run/docker.sock:/var/run/docker.sock \
willfarrell/autoheal
HTTP Healthcheck Endpointβ
Dashy also exposes an unauthenticated HTTP liveness endpoint at /healthz, which returns a 200 with a small JSON body (status, uptime, version). It bypasses auth and SSL redirection so probes keep working regardless of how Dashy is configured.
Useful when fronting Dashy with a load balancer / reverse proxy that needs an HTTP probe for auto-failover, or when running on Kubernetes:
# Kubernetes
livenessProbe:
httpGet:
path: /healthz
port: 8080
# Traefik (label on the Dashy service)
- "traefik.http.services.dashy.loadbalancer.healthcheck.path=/healthz"
- "traefik.http.services.dashy.loadbalancer.healthcheck.interval=30s"
# Caddy (reverse_proxy block)
reverse_proxy dashy:8080 {
health_uri /healthz
health_interval 30s
}
Logs and Performanceβ
Container Logsβ
You can view logs for a given Docker container with docker logs [container-id], add the --follow flag to stream the logs. For more info, see the Logging Documentation. There's also Dozzle, a useful tool, that provides a web interface where you can stream and query logs from all your running containers from a single web app.
Container Performanceβ
You can check the resource usage for your running Docker containers with docker stats or docker stats [container-id]. For more info, see the Stats Documentation. There's also cAdvisor, a useful web app for viewing and analyzing resource usage and performance of all your running containers.
Management Appsβ
You can also view logs, resource usage and other info as well as manage your entire Docker workflow in third-party Docker management apps. For example Portainer an all-in-one open source management web UI for Docker and Kubernetes, or LazyDocker a terminal UI for Docker container management and monitoring.
Advanced Logging and Monitoringβ
Docker supports using Prometheus to collect logs, which can then be visualized using a platform like Grafana. For more info, see this guide. If you need to route your logs to a remote syslog, then consider using logspout. For enterprise-grade instances, there are managed services, that make monitoring container logs and metrics very easy, such as Sematext with Logagent.
Auto-Starting at System Bootβ
You can use Docker's restart policies to instruct the container to start after a system reboot, or restart after a crash. Just add the --restart=always flag to your Docker compose script or Docker run command. For more information, see the docs on Starting Containers Automatically.
For Podman, use Quadlet to run your container as a systemd service. A similar approach can be used with Docker, if you need to start containers after a reboot, but before any user interaction.
To restart the container after something within it has crashed, consider using docker-autoheal by @willfarrell, a service that monitors and restarts unhealthy containers. For more info, see the Healthchecks section above.
Updatingβ
Dashy is under active development, so to take advantage of the latest features, you may need to update your instance every now and again.
Updating Docker Containerβ
- Pull latest image:
docker pull lissy93/dashy:latest - Kill off existing container
- Find container ID:
docker ps - Stop container:
docker stop [container_id] - Remove container:
docker rm [container_id]
- Find container ID:
- Spin up new container:
docker run [params] lissy93/dashy
Or if you're using Docker Compose, it's just docker compose pull && docker compose up -d.
Automatic Docker Updatesβ
You can automate the above process using Watchtower.
Watchtower will watch for new versions of a given image on Docker Hub, pull down your new image, gracefully shut down your existing container and restart it with the same options that were used when it was deployed initially. Note that the original containrrr/watchtower project was archived in December 2025, so use the maintained fork linked above (nickfedor/watchtower on Docker Hub), which is a drop-in replacement.
To get started, spin up the watchtower container (it needs the Docker socket too):
docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
nickfedor/watchtower
For more information, see the Watchtower Docs
Updating Dashy from Sourceβ
Stop your current instance of Dashy, then navigate into the source directory. Pull down the latest code, with git pull origin master, then update dependencies with yarn, rebuild with yarn build, and start the server again with yarn start.
Upgrading Across Major Versionsβ
Major releases can include breaking changes, so skim the release notes before jumping versions. The two that catch most people out:
- v3 moved all user files (config, icons, etc) into a single
user-datadirectory, mounted at/app/user-data, replacing the old/app/public/conf.ymlmount. - v4 made the container run as a non-root user (uid 1000). If your mounted
user-dataisn't writable by that uid, config saves from the UI will fail. See File Ownership and Permissions.
To check a download is genuine, see Verifying Releases.
Backing Upβ
Dashy-Specific Backupβ
All configuration and dashboard settings are stored in your user-data/conf.yml file. If you provide additional assets (like icons, fonts, themes, etc), these will also live in the user-data directory. So to backup all Dashy data, this is the only directory you need to backup.
When you save config through the UI, Dashy automatically creates a timestamped backup in user-data/config-backups/ (configurable via the BACKUP_DIR env var). If you break your config, check that directory for a recent copy. Backups can be disabled by setting DISABLE_CONFIG_BACKUPS=true (e.g. on read-only filesystems or where permissions don't allow it).
Since Dashy is open source, there shouldn't be any need to backup the main container.
Dashy also has a built-in cloud backup feature, which is free for personal users, and will let you make and restore fully encrypted backups of your config directly through the UI. To learn more, see the Cloud Backup Docs
Backing Up Volumesβ
offen/docker-volume-backup is a useful tool for periodic Docker volume backups, to any S3-compatible storage provider. It's run as a light-weight Docker container, and is easy to setup, and also supports GPG-encryption, email notification, and routing away older backups.
To get started, create a docker-compose similar to the example below, and then start the container. For more info, check out their documentation, which is very clear.
services:
backup:
image: offen/docker-volume-backup:latest
environment:
BACKUP_CRON_EXPRESSION: "0 * * * *"
BACKUP_PRUNING_PREFIX: backup-
BACKUP_RETENTION_DAYS: 7
AWS_BUCKET_NAME: backup-bucket
AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
volumes:
- data:/backup/my-app-backup:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
volumes:
data:
It's worth noting that this process can also be done manually, using the following commands:
Backup:
docker run --rm -v some_volume:/volume -v /tmp:/backup alpine tar -cjf /backup/some_archive.tar.bz2 -C /volume ./
Restore:
docker run --rm -v some_volume:/volume -v /tmp:/backup alpine sh -c "rm -rf /volume/* /volume/..?* /volume/.[!.]* ; tar -C /volume/ -xjf /backup/some_archive.tar.bz2"
Schedulingβ
If you need to periodically schedule the running of a given command on Dashy (or any other container), then a useful tool for doing so it ofelia. This runs as a Docker container and is really useful for things like backups, logging, updating, notifications, etc. Crons are specified using Go's crontab format, and a useful tool for visualizing this is crontab.guru. This can also be done natively with Alpine: docker run -it alpine ls /etc/periodic.
I recommend combining this with healthchecks for easy monitoring of jobs, and failure notifications.
SSL Certificatesβ
Enabling HTTPS with an SSL certificate is recommended, especially if you are hosting Dashy anywhere other than your home. This will ensure that all traffic is encrypted in transit.
Auto-SSLβ
If you are using NGINX Proxy Manager, then SSL is supported out of the box. Once you've added your proxy host and web address, then set the scheme to HTTPS, then under the SSL Tab select "Request a new SSL certificate" and follow the on-screen instructions.
If you're hosting Dashy behind Cloudflare, then they offer free and easy SSL- all you need to do is enable it under the SSL/TLS tab. Or if you are using shared hosting, you may find this tutorial helpful.
Getting an SSL Certificateβ
Let's Encrypt is a global Certificate Authority, providing free SSL/TLS Domain Validation certificates in order to enable secure HTTPS access to your website. They have good browser/ OS compatibility, support wildcard certificates via DNS-01, and provide CertBot, an easy app for generating and setting up an SSL certificate.
This process can be automated, using something like the Docker-NGINX-Auto-SSL Container to generate and renew certificates when needed.
If you're not so comfortable on the command line, then you can use a tool like ZeroSSL to generate your cert. They also provide step-by-step setup instructions for most platforms.
Passing an SSL Certificate to Dashyβ
Once you've generated your SSL cert, you'll need to pass it to Dashy. This can be done by specifying the paths to your public and private keys using the SSL_PRIV_KEY_PATH and SSL_PUB_KEY_PATH environmental variables. Or if you're using Docker, then just pass public + private SSL keys in under /etc/ssl/certs/dashy-pub.pem and /etc/ssl/certs/dashy-priv.key respectively, e.g:
docker run -d \
-p 8080:8080 \
-v ~/my-private-key.key:/etc/ssl/certs/dashy-priv.key:ro \
-v ~/my-public-key.pem:/etc/ssl/certs/dashy-pub.pem:ro \
lissy93/dashy:latest
Dashy runs as uid 1000 inside the container, so both files need to be readable by that user (Certbot's private keys are root-only by default). If the key can't be read, Dashy will still start on HTTP but HTTPS silently won't, with an EACCES error in the logs.
By default the SSL port is 443 within a Docker container, or 4001 if running on bare metal, but you can override this with the SSL_PORT environmental variable.
Once everything is setup, you can verify your site is secured using a tool like SSL Checker.
Authenticationβ
Dashy has built-in auth (users defined in your config), and supports Keycloak and any OIDC provider (Authentik, Authelia, Zitadel, Pocket ID, etc), header auth from a reverse proxy, and HTTP basic auth on the server endpoints. Usage instructions for all of these, as well as alternative auth methods, are on the Authentication Docs page.
Network Exposureβ
Dashy is designed to run on your local network, behind your firewall. If you only access it from within your home or over a VPN, the defaults are fine.
If you do need to expose Dashy to the internet, you should put it behind a reverse proxy with its own authentication layer (e.g. Authelia, Authentik, Cloudflare Access, or your proxy's built-in auth). Don't rely solely on Dashy's built-in auth for internet-facing instances - it's a convenience feature for private networks, not a hardened perimeter control. See the Authentication Docs for setup options.
When Dashy runs in server mode (the default Docker setup), it exposes several API endpoints for things like status checks, ping checks, config saving, system info, and a CORS proxy used by widgets. When authentication is enabled (via ENABLE_HTTP_AUTH=true or BASIC_AUTH_USERNAME/BASIC_AUTH_PASSWORD env vars), all of these endpoints require valid credentials. Without auth configured, they are open. That's fine for private networks, but not appropriate for public access.
The CORS proxy (/cors-proxy) is worth calling out specifically: it forwards requests from the Dashy server to external URLs, so widgets can reach APIs that don't set CORS headers. On a private network this is harmless, but on an internet-exposed instance without auth, it could be abused as an open proxy. Always enable authentication if your instance is reachable from untrusted networks.
If you don't use widgets or status checks, you can disable the endpoints which make outbound requests (/status-check, /ping-check and /cors-proxy) entirely, by setting the DISABLE_PROXY_ENDPOINTS=true environmental variable. They will then respond with a 403 error, and never make an external request.
Managing Containers with Docker Composeβ
When you have a lot of containers, it quickly becomes hard to manage with docker run commands. The solution to this is docker compose, a handy tool for defining all a containers run settings in a single YAML file, and then spinning up that container with a single short command - docker compose up. A good example of which can be seen in @abhilesh's docker compose collection.
You can use Dashy's default docker-compose.yml file as a template, and modify it according to your needs.
An example Docker compose, using the default base image from DockerHub, might look something like this:
services:
dashy:
container_name: Dashy
image: lissy93/dashy:latest
volumes:
- ./user-data:/app/user-data
ports:
- 4000:8080
restart: unless-stopped
healthcheck:
test: ['CMD', 'node', '/app/services/healthcheck.js']
interval: 1m30s
timeout: 10s
retries: 3
start_period: 30s
Passing in Environmental Variablesβ
With Docker, you can define environmental variables under the environment section of your Docker compose file, or with the --env flag on docker run (or --env-file if you've got many). They're used for server-level settings, read before the config file is loaded. All are optional.
| Variable | Description |
|---|---|
PORT | Port to listen on. Defaults to 8080 in Docker |
USER_DATA_DIR | Path to the user data directory. Defaults to user-data |
BACKUP_DIR | Where config backups are written. Defaults to user-data/config-backups |
DISABLE_CONFIG_BACKUPS | Set to true to skip the backup made before each config save |
ENABLE_HTTP_AUTH | Set to true to require the users defined in conf.yml on all server endpoints |
BASIC_AUTH_USERNAME / BASIC_AUTH_PASSWORD | Alternatively, protect the server endpoints with a single static login |
DISABLE_PROXY_ENDPOINTS | Set to true to turn off the status check, ping check and CORS proxy endpoints |
ENABLE_API | Set to true to enable the REST API, see the API Docs |
API_TOKEN | Bearer token granting full API access |
SSL_PRIV_KEY_PATH / SSL_PUB_KEY_PATH | Paths to your SSL key and certificate, see SSL Certificates |
SSL_PORT | HTTPS port. Defaults to 443 in Docker |
REDIRECT_HTTPS | Set to false to stop HTTP redirecting to HTTPS when SSL is enabled |
Anything starting with VITE_ is a build-time variable, baked in when the app is compiled, so it has no effect on the published image. If you need one of those (e.g. VITE_APP_ROUTING_MODE=hash), you'll need to build your own container. The full list is in the developing docs.
Setting Headersβ
Any external requests made to a different origin (app/ service under a different domain) will be blocked if the correct headers are not specified. This is known as Cross-Origin Resource Sharing (CORS) and is a security feature built into modern browsers.
Note that widgets fetch data through Dashy's own server-side proxy (useProxy: true), which avoids CORS entirely. And embedding a service in an iframe (workspace view or modals) is controlled by that service's X-Frame-Options / Content-Security-Policy: frame-ancestors headers, not CORS.
If you see a CORS error in your console, this can be easily fixed by setting the correct headers. This is not a bug with Dashy, so please don't raise it as a bug!
Example Headersβ
The following section briefly outlines how you can set headers for common web proxies/ servers. More info can be found in the documentation for the proxy that you are using, or in the MDN Docs.
These examples are using:
Access-Control-Allow-Originheader, but depending on what type of content you are enabling, this will vary. For example, to allow a site to be loaded in an iframe (for the modal or workspace views) you would useX-Frame-Options.- The domain root (
/), if your're hosting from a sub-page, replace that with your path. - A wildcard (
*), which would allow access from traffic on any domain, this is discouraged, and you should replace it with the URL where you are hosting Dashy. Note that for requests that transport sensitive info, like credentials (e.g. Keycloak login), the wildcard is disallowed all together and will be blocked.
Caddyβ
See Caddy
headerdocs for more info.
headers / {
Access-Control-Allow-Origin *
}
NGINXβ
See NGINX
ngx_http_headers_moduledocs for more info.
location / {
add_header Access-Control-Allow-Origin *;
}
Note this can also be done through the UI, using NGINX Proxy Manager.
Traefikβ
See TrΓ¦fΙͺk CORS headers docs for more info.
labels:
- "traefik.http.middlewares.testheader.headers.accesscontrolallowmethods=GET,OPTIONS,PUT"
- "traefik.http.middlewares.testheader.headers.accesscontrolalloworiginlist=https://foo.bar.org,https://example.org"
- "traefik.http.middlewares.testheader.headers.accesscontrolmaxage=100"
- "traefik.http.middlewares.testheader.headers.addvaryheader=true"
HAProxyβ
See HAProxy Rewrite Response Docs for more info.
/
http-response add-header Access-Control-Allow-Origin *