Skip to content

Install

Valet runs as one container. It needs two folders of yours: config for config.yaml, and data for everything it keeps.

Make a folder with this docker-compose.yml, which is docker-compose.example.yml from the repo without its comments.

docker-compose.yml
services:
valet:
image: ghcr.io/askvalet/valet:latest
container_name: valet
restart: unless-stopped
environment: { TZ: America/Toronto }
env_file: .env
volumes:
- ./config:/config
- ./data:/app/data
ports:
- 127.0.0.1:7575:7575
read_only: true
tmpfs: [/tmp]
cap_drop: [ALL]
security_opt: [no-new-privileges:true]

Then create the folders and an empty .env, and start it:

Terminal window
mkdir config data
touch .env
sudo chown -R 1000:1000 config data # the container runs as the node user, uid 1000
docker compose up -d

Open http://localhost:7575.

On first start Valet writes a starter config/config.yaml and shows a getting-started card. If it can’t write to that folder, it runs the starter from memory and the card says so.

Terminal window
docker run -d --name valet -p 127.0.0.1:7575:7575 \
-v ./config:/config -v ./data:/app/data --env-file .env \
ghcr.io/askvalet/valet:latest

This leaves out the hardening flags. Prefer Compose, or add --read-only --tmpfs /tmp --cap-drop ALL --security-opt no-new-privileges:true.

Each release publishes ghcr.io/askvalet/valet for amd64 and arm64, tagged with its version and latest. To build the image yourself instead, the Dockerfile needs nothing but the repo.

Terminal window
git clone https://github.com/askvalet/valet
cd valet
docker build -t local/valet:dev .

Then use image: local/valet:dev in your Compose file.

LineWhy
127.0.0.1:7575:7575This machine only. Use 7575:7575 for your whole network, after reading sign-in and network safety.
./config:/configA folder, not the file, so edits show up live. Add :ro if the assistant should never save config changes.
./data:/app/dataWallpaper, icon cache, uploads, history, accounts and plugins.
env_file: .envSecrets that config.yaml refers to as ${NAME}. The file has to exist, even if it’s empty.
read_only, tmpfsThe container’s own filesystem can’t be written. Only the two mounts and /tmp can.
cap_drop, no-new-privilegesNo Linux capabilities, and no way to gain any.
TZThe time zone for times the server writes. You can also set timezone: in config.yaml.

The image has no node_modules: the server is bundled into one folder of JavaScript. It has a health check on /healthz.

VariableDefaultWhat it does
VALET_CONFIGconfig.yaml, or /config/config.yaml in DockerThe config file.
VALET_DATAdata, or /app/data in DockerWhere Valet keeps its files.
PORT7575The port it listens on.
HOST0.0.0.0The address it listens on.

Valet stays in its box. To show containers, the docker integration reads through a read-only socket proxy. Never mount the Docker socket into Valet. The commented-out valet-dockerproxy service in docker-compose.example.yml is ready to use.

For systemd services, the host integration talks to a small read-only agent, agent/valet-agent.py, which you run on the machine.

You need Node 22.18 or newer.

Terminal window
git clone https://github.com/askvalet/valet
cd valet
npm install
npm run build
cp config.example.yaml config.yaml # then edit it
npm start

npm start reads .env if there is one.

Write your first config.