Repository: promptfoo/promptfoo
Issue: can't launch self-hosted promptfoo on ubuntu
Root cause: Documentation Deficiency

**Describe the bug**
docker run errors with
```
node:fs:2368
    return binding.writeFileUtf8(
                   ^

Error: EACCES: permission denied, open '/home/promptfoo/.promptfoo/evalLastWritten'
    at Object.writeFileSync (node:fs:2368:20)
    at ensureSignalFile (/app/dist/src/database/signal.js:36:22)
    at setupSignalWatcher (/app/dist/src/database/signal.js:47:5)
    at startServer (/app/dist/src/server/server.js:223:37)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  errno: -13,
  code: 'EACCES',
  syscall: 'open',
  path: '/home/promptfoo/.promptfoo/evalLastWritten'
}

Node.js v20.18.3
```

**To Reproduce**
I'm using vanilla example from the docs:
```
$ sudo docker run -d --name promptfoo_container -p 3000:3000 -v ~/promptfoo_files:/home/promptfoo/.promptfoo ghcr.io/promptfoo/promptfoo:latest 
```

**Expected behavior**
I can launch pormptfoo UI dashboard.

**Screenshots**
n/a

**System information:**

```
Ubuntu 24.04.2 LTS (GNU/Linux 6.8.0-1021-azure x86_64)
```

`promptfoo debug` errors too
```
$ npx promptfoo debug
Need to install the following packages:
  promptfoo@0.105.1
Ok to proceed? (y) y
npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE   package: 'lru-cache@11.0.2',
npm WARN EBADENGINE   required: { node: '20 || >=22' },
npm WARN EBADENGINE   current: { node: 'v18.19.1', npm: '9.2.0' }
npm WARN EBADENGINE }
npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE   package: 'undici@7.4.0',
npm WARN EBADENGINE   required: { node: '>=20.18.1' },
npm WARN EBADENGINE   current: { node: 'v18.19.1', npm: '9.2.0' }
npm WARN EBADENGINE }
npm WARN deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm WARN deprecated rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported
```

- Promptfoo version: `0.105.1`

**Additional context**
?

PS I built [from source](https://www.promptfoo.dev/docs/usage/self-hosting/#building-from-source) and got the same thing.

Hi @dokato,

Thanks for reporting this issue. The error you're seeing is a permissions problem related to how Docker volumes work:

```
Error: EACCES: permission denied, open '/home/promptfoo/.promptfoo/evalLastWritten'
```

## The Problem

The Docker container runs with a non-root user (`promptfoo`), and when you mount a volume from your host to the container's `/home/promptfoo/.promptfoo` directory, the `promptfoo` user inside the container doesn't have permissions to write to it.

## Solutions

Here are a few ways to fix this:

### Option 1: Use Docker Compose with a named volume

This is the recommended approach as Docker automatically manages the volume's permissions:

```yaml
version: '3'

services:
  promptfoo:
    image: ghcr.io/promptfoo/promptfoo:latest
    ports:
      - "3000:3000"
    volumes:
      - promptfoo_data:/home/promptfoo/.promptfoo

volumes:
  promptfoo_data:
```

Run with `docker-compose up -d`

### Option 2: Fix permissions on the host directory

If you prefer using the `docker run` command directly:

```bash
# Create the directory with proper permissions
mkdir -p ~/promptfoo_files
chmod 777 ~/promptfoo_files

# Run the container
docker run -d --name promptfoo_container \
  -p 3000:3000 \
  -v ~/promptfoo_files:/home/promptfoo/.promptfoo \
  ghcr.io/promptfoo/promptfoo:latest
```

### Option 3: Run a temporary container to fix permissions

```bash
# Create the directory
mkdir -p ~/promptfoo_files

# Run a container to fix permissions
docker run --rm -v ~/promptfoo_files:/data alpine chmod -R 777 /data

# Run promptfoo
docker run -d --name promptfoo_container \
  -p 3000:3000 \
  -v ~/promptfoo_files:/home/promptfoo/.promptfoo \
  ghcr.io/promptfoo/promptfoo:latest
```

Let us know if you have any questions!


Closing for now but please feel free to comment or re-open if that doesn't work for you