Paneo is a keyboard-first, two-pane file manager inspired by Midnight Commander and Total Commander.
Built with Nuxt 4 + Nuxt UI, it is designed to run in Docker with filesystem access restricted to configured roots.
- Two fully independent panels
- Optional password auth (enabled when
NUXT_PANEO_AUTH_PASSWORDis set) - Configurable source roots from environment variables
- File/folder operations: create, rename, copy, move, delete
- Multi-select support (keyboard + mouse)
- File viewer and text editor
- Favorites for folders:
F2opens favorites modal- star icon in folder rows to add/remove favorites
Ftoggles favorite for current selected folder
- Upload with drag-and-drop and file/folder picker (
F9) - Download files/folders to local machine (
F10)- single file downloads directly
- multiple selected items are downloaded as
.tar.gz
- Progress tracking for copy/upload (modal + toast, minimize/cancel)
- Keyboard workflow (
Tab, arrows,PgUp/PgDn,Enter,F1-F10,Insert,T,F) - Localization: RU, EN, Traditional Chinese, German, Spanish
Minimal example:
UID=1000
GID=1000
HOST_DOCS=/home/user/Documents
NUXT_FILE_MANAGER_ROOTS="docs=/data/source-1"
# Persist paneo internal data (favorites, service state)
PANEO_DATA_DIR=./paneo-dataAt least one host source variable is required.
Variable names can be any names (for example HOST_DOCS, HOST_MEDIA, DATA_A), but names must match between .env and docker-compose.yml.
Full example (with optional auth variables):
UID=1000
GID=1000
HOST_DOCS=/home/user/Documents
HOST_MEDIA=/mnt/storage
NUXT_FILE_MANAGER_ROOTS="docs=/data/source-1;media=/data/source-2"
NUXT_PANEO_AUTH_PASSWORD=
NUXT_PANEO_AUTH_SECRET=
NUXT_PANEO_AUTH_COOKIE_SECURE=false
PANEO_DATA_DIR=./paneo-dataMinimal docker-compose.yml (one source, no auth):
services:
paneo:
image: ghcr.io/codewec/paneo:latest
container_name: paneo
user: "${UID}:${GID}"
restart: unless-stopped
ports:
- "3000:3000"
env_file:
- .env
volumes:
- ${HOST_DOCS}:/data/source-1
- ${PANEO_DATA_DIR}:/app/.paneoFull docker-compose.yml (two sources + optional auth vars):
services:
paneo:
image: ghcr.io/codewec/paneo:latest
container_name: paneo
user: "${UID}:${GID}"
restart: unless-stopped
ports:
- "3000:3000"
env_file:
- .env
volumes:
- ${HOST_DOCS}:/data/source-1
- ${HOST_MEDIA}:/data/source-2
- ${PANEO_DATA_DIR}:/app/.paneoYou can use other source variable names. The important part is consistency between:
.envdocker-compose.yml
For example, if you use HOST_DOCS in .env, use ${HOST_DOCS} in volumes.
All runtime variables are read from .env through env_file (no environment section required).
Persistent data mount:
services:
paneo:
volumes:
- ${PANEO_DATA_DIR}:/app/.paneoThis mount is recommended so favorites and paneo internal data are not lost when the container is recreated.
docker compose up -dOpen: http://localhost:3000
docker compose down- Uses prebuilt image:
ghcr.io/codewec/paneo:latest - Suitable for end users and deployment
Run:
docker compose up -d- Builds local image
- Runs Nuxt in development mode with source mounted
Run:
docker compose -f docker-compose.dev.yml up --buildStop:
docker compose -f docker-compose.dev.yml downThe app only works inside explicitly allowed roots from this variable.
Example:
NUXT_FILE_MANAGER_ROOTS="docs=/data/source-1;storage=/data/source-2;/tmp"Supported formats:
;-separated entries- newline-separated entries
- optional alias syntax:
alias=/path
If no root is selected in a panel, it shows the source list.
Common .env variables:
UIDGID- host source variables (any names, at least one)
NUXT_FILE_MANAGER_ROOTSNUXT_PANEO_AUTH_PASSWORD(optional)NUXT_PANEO_AUTH_SECRET(optional, recommended)NUXT_PANEO_AUTH_COOKIE_SECURE(optional, default:false)NITRO_HOSTandNITRO_PORT(optional; defaults are usually enough)PANEO_DATA_DIR(recommended)
Simple meaning:
UIDandGIDare your Linux user/group IDs. Container runs with them, so new files are created with your host user ownership.- Host source variables are folders on your host (your real disk).
- You can use any variable names, but they must match in both:
.envdocker-compose.ymlvolume definitions
NUXT_FILE_MANAGER_ROOTStells paneo what to show in the root list.- In
NUXT_FILE_MANAGER_ROOTSyou must use container paths (/data/source-*), not host paths. NUXT_PANEO_AUTH_PASSWORDenables authentication. Leave empty to disable auth.NUXT_PANEO_AUTH_SECRETsigns auth session cookies. If not set, a fallback secret is derived from password.NUXT_PANEO_AUTH_COOKIE_SECUREcontrols thesecureflag for auth cookie:- use
trueonly when app is served over HTTPS - keep
falsefor HTTP (including local/dev HTTP)
- use
NITRO_HOSTandNITRO_PORTare optional runtime overrides.PANEO_DATA_DIRis a host folder mounted into/app/.paneoto persist paneo internal data.- Favorites file path is fixed internally as
/app/.paneo/favorites.json.
Important:
- At least one source folder must be mounted.
- Source variable names can be arbitrary, but must be consistent between
.envanddocker-compose.yml. - Mounted container paths must be listed in
NUXT_FILE_MANAGER_ROOTS. NUXT_PANEO_AUTH_COOKIE_SECURE=trueshould be used only for HTTPS.- For persistent favorites, mount
PANEO_DATA_DIRto/app/.paneo.
If these paths do not match, source navigation or persistence will fail.
- Nuxt 4
- Nuxt UI
- Nitro server routes (
/api/fs/*) @nuxtjs/i18n
Requirements:
- Node.js 20+
- pnpm
Commands:
pnpm install
pnpm dev
pnpm typecheck
pnpm lint
pnpm buildGenerate/update CHANGELOG.md:
pnpm changelogPreview changelog changes without writing:
pnpm changelog:drydry alias (same as changelog:dry):
pnpm dryCreate a release with version bump, changelog update, commit and tag:
pnpm releaseCurrent scripts use --hideAuthorEmail, so author emails are not included in release notes.
If you also want to hide the contributors block completely, use --noAuthors.
Recommended release flow:
- Ensure branch is up to date and checks pass.
- Preview notes:
pnpm dry. - Create release commit + tag:
pnpm release. - Push commit and tag:
git push --follow-tags. - GitHub Actions (
docker-publish) publishes release images from the pushed tag.
app/pages/index.vue- file manager workspace pageapp/pages/auth.vue- auth page (SSR redirect target)app/components/file-manager/FileManagerWorkspace.vue- workspace orchestration (panels + modal wiring)app/components/file-manager/*- panel/action/startup/modal UI componentsapp/composables/useFileManagerPanels.ts- panel state, navigation, selectionapp/composables/useFileManagerActions.ts- operations, progress tasks, action gatingapp/composables/useFileManagerFavorites.ts- favorites state and interactionsapp/composables/usePaneoAuth.ts- auth actions (login/logout)server/middleware/auth-redirect.ts- SSR route guard (/<->/auth)server/middleware/auth-api.ts- API auth guard (/api/*)server/utils/auth-session.ts- signed session cookie helpersapp/composables/useFileManagerApi.ts- frontend API clientapp/composables/useFileManagerHotkeys.ts- keyboard shortcutsserver/utils/file-manager.ts- secure filesystem logic (normalization, root sandboxing)server/utils/favorites-store.ts- server-side favorites storageserver/utils/startup-status.ts- startup validation (roots/access/UID/GID warning)server/api/fs/*- server API handlersserver/api/system/startup.get.ts- startup status endpointi18n/locales/*- localization files
- All filesystem operations are constrained to configured roots.
- Path traversal outside roots is rejected.
- Potentially destructive actions require confirmation.
Drag-and-drop behavior may differ across browser/desktop combinations.
If DnD is limited in your environment, use F9 Upload as fallback.