A self-hosted web frontend for managing files on disk. Browse, create, edit, upload, download, rename, move, delete, and work with zip archives — all from the browser. It runs as a single container and serves files from a directory you mount into it, making it a natural fit for Podman, Docker and Kubernetes with a persistent volume.

.., symlinks, absolute paths).linux/amd64 and linux/arm64.The image is published on Docker Hub as
pfeiffermax/file-manager.
Mount a host directory at /data and open http://localhost:8080:
podman run --rm -p 8080:8080 -v "$PWD/files:/data" pfeiffermax/file-manager
# or: docker run --rm -p 8080:8080 -v "$PWD/files:/data" pfeiffermax/file-manager
By default authentication is disabled (AUTH_METHOD=none) — anyone who can
reach the port can manage the files. Enable Basic Auth or Keycloak (see below)
before exposing it beyond localhost.
All configuration is provided through environment variables and read by the
backend at startup. The frontend receives only what it needs (auth method and
public Keycloak settings) from GET /api/config. The backend validates the
configuration on startup and fails fast with a clear error if a variable
required by the active AUTH_METHOD is missing.
| Variable | Default | Description |
|---|---|---|
PORT | 8080 | HTTP listen port. |
HOST | 0.0.0.0 | HTTP listen address. |
FILES_ROOT | /data | Root directory of the managed files; all operations are sandboxed to this path. |
AUTH_METHOD | none | Active authentication method: basic, keycloak, or none. |
AUTH_USERNAME | — | Basic Auth username (required when AUTH_METHOD=basic). |
AUTH_PASSWORD | — | Basic Auth password (required when AUTH_METHOD=basic). |
KEYCLOAK_URL | — | Keycloak URL as reached by the browser; determines the token issuer (required when AUTH_METHOD=keycloak). |
KEYCLOAK_INTERNAL_URL | KEYCLOAK_URL | Keycloak URL the backend uses to fetch the realm JWKS. Set it when Keycloak is only reachable internally under a different hostname (compose network, in-cluster service). |
KEYCLOAK_REALM | — | Keycloak realm name (required when AUTH_METHOD=keycloak). |
KEYCLOAK_CLIENT_ID | — | Keycloak client ID (required when AUTH_METHOD=keycloak). |
See .env.example for a copy-paste starting point.
The active method is chosen with AUTH_METHOD; the frontend learns it at
runtime from GET /api/config and adapts its login flow accordingly.
none (default) — no authentication. Suitable only for trusted networks
or local use.basic — HTTP Basic Auth enforced on every /api/* route with a
timing-safe comparison of AUTH_USERNAME / AUTH_PASSWORD. The app shows its
own login form (no browser-native prompt); credentials are kept in memory only
and attached as an Authorization: Basic … header.keycloak — OAuth2/OIDC using the Authorization Code flow with PKCE.
Tokens are refreshed silently, and the backend validates Bearer tokens against
the realm JWKS using jose.The application is designed to run 24/7 in a container on Kubernetes. No manifests are shipped in this repository, but the notes below cover everything you need.
Volume — mount a PersistentVolumeClaim at FILES_ROOT (default /data).
The container runs as the non-root node user, so the volume must be writable
by that user.
Health probes — the backend exposes an always-unauthenticated
GET /healthz (outside /api/*) returning 200 OK. Use it for both liveness
and readiness probes.
Credentials — never bake credentials into the image or a plain
Deployment. Inject AUTH_USERNAME / AUTH_PASSWORD (or the Keycloak
settings) from a Secret via envFrom or env.valueFrom.secretKeyRef.
# Illustrative Deployment snippet (not a complete manifest).
containers:
- name: file-manager
image: pfeiffermax/file-manager:latest
ports:
- containerPort: 8080
env:
- name: AUTH_METHOD
value: basic
envFrom:
- secretRef:
name: file-manager-credentials # AUTH_USERNAME / AUTH_PASSWORD
volumeMounts:
- name: files
mountPath: /data
livenessProbe:
httpGet:
path: /healthz
port: 8080
readinessProbe:
httpGet:
path: /healthz
port: 8080
volumes:
- name: files
persistentVolumeClaim:
claimName: file-manager-data
When using Keycloak, set KEYCLOAK_URL to the browser-facing issuer URL and, if
Keycloak is reachable in-cluster under a different service hostname, set
KEYCLOAK_INTERNAL_URL to that internal address for JWKS fetching.
Prerequisites: Node 24 (see .nvmrc) and pnpm (managed via
Corepack — the version is pinned in package.json).
corepack enable
pnpm install
pnpm dev
pnpm dev runs the full stack: the Fastify API on http://localhost:3000
(tsx watch) and the Vite dev server on http://localhost:5173, which proxies
/api and /healthz to the API. During development the managed files live in
.dev-data/ (override with FILES_ROOT).
| Command | Description |
|---|---|
pnpm dev | Full dev stack (API + Vite). |
pnpm dev:web | Vite dev server only. |
pnpm dev:server | Backend only (tsx watch). |
pnpm build | Build the SPA (Vite) and backend (tsc) into dist/. |
pnpm start | Run the built server (production entrypoint). |
pnpm lint | ESLint with --fix. |
pnpm format | Prettier write. |
pnpm typecheck | vue-tsc (frontend) and tsc (backend), no emit. |
pnpm test:unit | Vitest unit tests. |
pnpm test:e2e | Playwright end-to-end tests. |
A Podman Compose stack brings up the app behind a preconfigured Keycloak:
podman compose up --build
test / test)admin / admin)The realm file-manager and client file-manager are imported from
compose/keycloak/realm.json. The test user is
for local testing only.
The image uses a multi-stage Containerfile and is built with
Podman:
podman build -t file-manager -f Containerfile .
podman run --rm -p 8080:8080 -v "$PWD/files:/data" file-manager
Single package — the Vue 3 SPA (src/) and the Fastify backend (server/)
share one package.json and one toolchain. In production a single process
serves the built SPA via @fastify/static and the file API under /api/*.
src/ — Vue 3 frontend (SPA): VueFinder UI, Pinia stores, router.server/ — Fastify backend: file API, auth, config, path sandboxing.e2e/ — Playwright end-to-end tests.compose/ — assets for local manual testing (Keycloak realm import)..github/workflows/ — CI and release automation.See CLAUDE.md for the full architecture and conventions
reference.
.github/workflows/ci.yaml) runs
linting, format checks, type checking and unit tests on every pull request.main opens a release PR, and merging it tags
a semantic version.latest.main is protected — never commit to it directly.feature/<short-kebab-name> or bugfix/<short-kebab-name>.main; CI must pass before merge.See LICENSE.
Content type
Image
Digest
sha256:48df6b452…
Size
73.3 MB
Last updated
2 months ago
docker pull pfeiffermax/file-manager