{"id":50343074,"url":"https://github.com/kekyo/uplodah","last_synced_at":"2026-05-29T18:30:27.254Z","repository":{"id":349716786,"uuid":"1202912849","full_name":"kekyo/uplodah","owner":"kekyo","description":"Universal file upload/download server 📤","archived":false,"fork":false,"pushed_at":"2026-04-24T01:11:09.000Z","size":980,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-29T18:30:24.188Z","etag":null,"topics":["downloader","file","instantly","npm","server","uploader"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kekyo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-06T14:28:54.000Z","updated_at":"2026-04-24T01:11:03.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kekyo/uplodah","commit_stats":null,"previous_names":["kekyo/uplodah"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/kekyo/uplodah","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2Fuplodah","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2Fuplodah/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2Fuplodah/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2Fuplodah/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kekyo","download_url":"https://codeload.github.com/kekyo/uplodah/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2Fuplodah/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33666290,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-29T02:00:06.066Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["downloader","file","instantly","npm","server","uploader"],"created_at":"2026-05-29T18:30:26.478Z","updated_at":"2026-05-29T18:30:27.242Z","avatar_url":"https://github.com/kekyo.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# uplodah\n\nSimple and modern universal file upload/download server.\n\n![uplodah](./images/uplodah-120.png)\n\n[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![npm version](https://img.shields.io/npm/v/uplodah.svg)](https://www.npmjs.com/package/uplodah)\n[![Docker Image Version](https://img.shields.io/docker/v/kekyo/uplodah.svg?label=docker)](https://hub.docker.com/r/kekyo/uplodah)\n\n---\n\n[(For Japanese language/日本語はこちら)](./README_ja.md)\n\n\u003e Please note that this English version of the document was machine-translated and then partially edited, so it may contain inaccuracies.\n\u003e We welcome pull requests to correct any errors in the text.\n\n## What Is This?\n\nThere are many situations, especially in private environments, where you want to host simple file exchange on your own server.\nSharing files with friends, small-office coworkers, or even customers often falls into this category.\n\nToday, cloud storage is the common answer.\nAt the same time, placing confidential files in cloud storage, even temporarily, may feel uncomfortable, or may be prohibited by strict organizational policy.\n\nSo how do you handle simple file upload and download without turning it into a large infrastructure task?\nDo you prepare Apache or Nginx, manually tune it, enable WebDAV, then decide what clients should use and how users will browse the stored files?\n\nThis \"uplodah\" may be what you are looking for.\nIt is a simple server implementation built on Node.js, focused specifically on uploading and downloading files.\n\nSetup is very easy, and zero-config operation is possible in many cases.\nThere is no database to manage.\nIf you need backups, just copy the storage subdirectory as files.\nRestoring it is equally straightforward and does not require any special tooling.\n\nIt also provides a modern browser-based UI:\n\n![Browse](./images/browse.png)\n\n- Browse uploaded files.\n- Search and organize them by file name or virtual directory.\n- Check download URLs for the latest version or a specific version.\n- Upload multiple files with drag and drop.\n- Copy ready-to-use `curl` API examples from the UI.\n\n### Key Features\n\n- **Quick setup, start an upload server in seconds**\n- No database required: uploaded files and metadata are managed directly on the filesystem\n- Simple upload API: just send `application/octet-stream` with `POST` or `PUT`\n- Versioned storage: re-uploading the same file name keeps history\n- Flexible downloads: retrieve either the latest version or a specific upload ID directly\n- Modern Web UI:\n  - File list, search, and expandable version view\n  - Sectioned display by virtual directory\n  - Multiple file upload\n  - Copyable API command examples\n  - Download selected files in bulk\n- Virtual storage rules:\n  - Per-directory store/delete control\n  - Per-directory expiration rules\n- Authentication: protect uploads only or the whole server with UI login, user roles, and API passwords\n- Supports reverse proxies and subpath hosting\n- Docker image available\n- Health check endpoint at `/health`\n\n## Requirements\n\nNode.js 20.19.0 or later\n\nUsed stack: Node.js, TypeScript, Vite, Vitest, prettier-max, screw-up, Fastify, React, React MUI, dayjs, JSON5, async-primitives\n\n---\n\n## Installation\n\nIf [Node.js](https://nodejs.org/ja/download) is not installed on your system yet, install it first.\n\n```bash\n$ node --version\nv24.11.1\n```\n\nOnce Node.js is available, install `uplodah` with npm:\n\n```bash\n$ npm install -g uplodah\n\nadded 157 packages in 8s\n\n42 packages are looking for funding\n  run `npm fund` for details\n```\n\nYou can also run it directly via `npx`:\n\n```bash\n$ npx uplodah\nNeed to install the following packages:\nuplodah@0.1.0\nOk to proceed? (y)\n\n[uplodah]: [2026/04/07 14:25:56.966]: [info]: uplodah [0.1.0] Starting...\n[uplodah]: [2026/04/07 14:25:56.967]: [info]: Config file: ./config.json\n[uplodah]: [2026/04/07 14:25:56.967]: [info]: Port: 5968\n[uplodah]: [2026/04/07 14:25:56.967]: [info]: Base URL: http://localhost:5968 (auto-detected)\n[uplodah]: [2026/04/07 14:25:56.967]: [info]: Storage directory: ./storage\n\n    :\n    :\n```\n\n## Usage\n\nHere are a few examples:\n\n```bash\n# Start the server on the default port 5968\nuplodah\n\n# Custom port and storage directory\nuplodah --port 3000 --storage-dir ./storage\n\n# Fix the public base URL behind a reverse proxy\nuplodah --base-url https://files.example.com/uplodah\n\n# Combine multiple options\nuplodah --port 3000 \\\n  --storage-dir ./storage \\\n  --config-file ./config.json \\\n  --max-upload-size-mb 500 \\\n  --max-download-size-mb 500\n```\n\nBy default, the following URLs are available:\n\n- Web UI: `http://localhost:5968/`\n- File listing API: `http://localhost:5968/api/files`\n- Upload API: `http://localhost:5968/api/upload/\u003cfile-name\u003e`\n- Download API: `http://localhost:5968/api/files/\u003cfile-name\u003e`\n- Health check: `http://localhost:5968/health`\n\nWhen `--base-url` is specified, the UI-generated download URLs and API command examples use that URL as their base.\n\n`--config-file` points to the configuration file and is useful when you want more detailed customization.\nThe file is optional. If the default behavior works for you, you do not need it.\n\n### Web UI\n\nThe Web UI is user-friendly and includes file browsing per directory, file filtering, upload support, and ready-to-copy `curl` usage examples:\n\n![File list](./images/filelist.png)\n\nThe UI also shows upload and download examples using `curl`, which makes CLI integration straightforward:\n\n![cURL example](./images/curl-example.png)\n\n### Uploading Files\n\nYou can upload files from the UI.\nIt supports drag and drop and makes uploading multiple files at the same time very easy:\n\n![Upload](./images/upload.png)\n\nYou can also upload files through the API.\nThe following example uploads `report.txt` into the root directory with `curl`:\n\n```bash\ncurl -X POST http://localhost:5968/api/upload/report.txt \\\n  -H \"Content-Type: application/octet-stream\" \\\n  --data-binary @./report.txt\n```\n\nYou can do the same with `PUT`:\n\n```bash\ncurl -X PUT http://localhost:5968/api/upload/report.txt \\\n  -H \"Content-Type: application/octet-stream\" \\\n  --data-binary @./report.txt\n```\n\nWhen the upload succeeds, the server returns `201 Created`.\nThe response body includes the stored `uploadId` and generated download URLs.\nThe `Location` header is also set to the download target.\n\nTODO: Add a JSON response example.\n\n### Uploading into Virtual Directories\n\nIf `storage` rules are configured, you can upload files into virtual subdirectories.\nIn the UI, the target directory is selected from the dropdown in the upload panel.\n\nWhen using the API, include the subdirectory path in the request URL.\nThe following example stores `report.txt` under `/foobar`:\n\n```bash\ncurl -X POST http://localhost:5968/api/upload/foobar/report.txt \\\n  -H \"Content-Type: application/octet-stream\" \\\n  --data-binary @./report.txt\n```\n\nThis API path is treated as the public file name `/foobar/report.txt`.\n\nNotes:\n\n- If `storage` is not configured, only plain file names such as `report.txt` are allowed.\n- If `storage` is configured, uploadable directories are limited to paths defined there.\n- Paths containing special characters should be encoded per URL segment.\n\n### Downloading Files\n\nTo download a file from the UI, open the entry from the file list and click the \"Download\" button for the desired version.\nAs described earlier, `uplodah` can store multiple versions of the same file.\nThose versions are distinguished by upload timestamp, and the list is shown from newest to oldest:\n\n![Download](./images/download.png)\n\nWhen using `curl`, you also need to specify which version to download.\nThe precise value is returned in the JSON response from the upload API, but if you know the timestamp-based ID, you can construct the URL directly.\n\nDownload the latest version by file name only:\n\n```bash\ncurl -L \"http://localhost:5968/api/files/report.txt\" -o ./report.txt\n```\n\nDownload a specific version in `YYYYMMDD_HHmmss_fff` format:\n\n```bash\ncurl -L \"http://localhost:5968/api/files/report.txt/20260406_203040_123\" -o ./report.txt\n```\n\nNote that if multiple uploads happen at exactly the same timestamp, the version identifier may gain a suffix such as `_1`, `_2`, and so on.\n\nWhen selecting multiple versions in the Web UI and downloading them as a batch, the server creates the ZIP archive as a background job and stores it in a temporary file until it is downloaded or expires. The ZIP file name uses the `{realm}_YYYYMMDD_HHmmss.zip` form, where the timestamp is generated by the browser in its local timezone. The temporary server-side file name does not reuse the browser-provided file name.\n\n### File Listing\n\nTo fetch the file list with `curl`:\n\n```bash\ncurl \"http://localhost:5968/api/files?skip=0\u0026take=20\"\n```\n\nThe listing API returns groups sorted by most recent upload first.\nEach group contains all versions for that file name.\n\n---\n\n## File Storage Configuration\n\n### Storage Location\n\nBy default, uploaded files are stored under `./storage`.\nYou can change this with the `--storage-dir` option or the `storageDir` setting:\n\n```bash\n# Use the default ./storage directory\nuplodah\n\n# Use a custom directory\nuplodah --storage-dir /srv/uplodah/storage\n```\n\nRelative paths provided via CLI options or environment variables are resolved from the current working directory.\n`storageDir` inside `config.json` is resolved relative to the directory containing that `config.json`.\n\n### Storage Layout\n\nAs described above, `uplodah` does not use any special database.\nIt only places directories and files under the storage directory.\n\nEach stored upload is recognized only when all of the following are true:\n\n- The version directory name can be parsed as an `uploadId`\n- `metadata.json` exists and contains valid JSON\n- A payload file exists in the same directory and its file name matches the parent file-group directory name\n\nThe storage tree mirrors the public path directly under the storage root:\n\n```text\nstorage/\n├── report.txt/\n│   └── 20260406_203040_123/\n│       ├── metadata.json\n│       └── report.txt\n└── bropdox/\n    └── report.txt/\n        └── 20260406_204512_918/\n            ├── metadata.json\n            └── report.txt\n```\n\nWhen `storage` rules are enabled, the directory segments from the public path are simply inserted before the file-group directory.\nNo separate internal tree is used.\n\n`uploadId` values are generated from timestamps in `YYYYMMDD_HHmmss_fff` format.\nIf multiple uploads collide within the same millisecond, a sequence suffix is appended.\n\n### Virtual Directory Rules\n\nYou can define rules for virtual directories in `config.json`.\nThe `storage` section is optional.\nIf it is not defined, uploads are accepted only at the root with plain file names.\n\nOnce `storage: { ... }` is defined, uploads must target a path under a configured virtual directory.\nWhen uploading into deeper subdirectories, the most specific matching virtual directory rule is applied.\nYou can also configure behavior per virtual directory.\n\nHere is an example `storage` section in `config.json`:\n\n```json\n{\n  \"port\": 5968,\n  \"storage\": {\n    // Enabled virtual directories\n    \"/\": {}, // (Root directory)\n    \"/bropdox\": {\n      // \"/bropdox\"\n      \"description\": \"Temporary sharing area\",\n      \"accept\": [\"store\", \"delete\"],\n      \"expireSeconds\": 86400 // Expire after 24 hours\n    },\n    \"/archive\": {\n      // \"/archive\"\n      \"description\": \"Long-term archive\",\n      \"accept\": [] // Read only\n    },\n    \"/archive/incoming\": {\n      \"accept\": [\"store\", \"delete\"]\n    } // \"/archive/incoming\"\n  }\n}\n```\n\nIn this example:\n\n- `/` accepts normal uploads\n- Uploads anywhere under `/bropdox` expire automatically after 24 hours\n- `/archive` accepts deletion but not uploads\n- `/archive/incoming` is more specific than `/archive`, so uploads are allowed there again under that subtree\n\nRule behavior:\n\n- Keys must always start with `/`\n- Backslashes and relative path segments such as `.` and `..` are not allowed\n- `description` is shown in the UI directory list and upload-directory selector\n- `accept` may contain `store` and/or `delete`; when omitted, both remain allowed for backward compatibility\n- The most specific matching directory rule is applied\n- Once `storage` is defined, uploads outside configured virtual directory subtrees are rejected\n  To allow uploads at the root directory and its descendants as well, include `/` explicitly as shown above\n\n### Backup and Restore\n\nBecause there is no database, backing up the storage directory is sufficient:\n\n```bash\ncd /your/server/base/dir\ntar -cf - ./storage | bzip2 -9 \u003e backup-storage.tar.bz2\n```\n\nTo restore, extract the archive and start `uplodah` again with the same `storageDir` setting.\n\nIf the directory structure is damaged, you can rebuild it manually as long as you preserve the required layout:\n\n1. Create a directory in the form `\u003cpublic-path\u003e/\u003cYYYYMMDD_HHmmss_fff[_num]\u003e/`.\n2. Place a valid JSON `metadata.json` into that directory.\n3. Place the payload file into the same directory, and name it exactly the same as the parent file-group directory.\n\nIf you modify the storage directory directly while `uplodah` is running, those changes are not reflected immediately.\nRestart `uplodah` afterward.\n\n---\n\n## Configuration\n\n`uplodah` supports configuration via command-line options, environment variables, and `config.json`.\n\nSettings are applied in the following order, from highest priority to lowest:\n\n1. Command-line options\n2. Environment variables\n3. `config.json`\n4. Default values\n\n## Configuration File Structure\n\nYou can specify a custom configuration file:\n\n```bash\n# Using a command-line option\nuplodah --config-file /path/to/config.json\n\n# Using an environment variable\nexport UPLODAH_CONFIG_FILE=/path/to/config.json\nuplodah\n```\n\nIf not specified, `uplodah` looks for `./config.json` in the current directory.\n\n`config.json` is parsed as JSON5, so comments and trailing commas are allowed.\n\n### `config.json` Structure\n\n```json\n{\n  \"port\": 5968,\n  \"baseUrl\": \"https://files.example.com/uplodah\",\n  \"storageDir\": \"./storage\",\n  \"usersFile\": \"./users.json\",\n  \"realm\": \"Awesome uplodah\",\n  \"logLevel\": \"info\",\n  \"trustedProxies\": [\"127.0.0.1\", \"::1\"],\n  \"authMode\": \"none\",\n  \"sessionSecret\": \"\u003cyour-secret-here\u003e\",\n  \"passwordMinScore\": 2,\n  \"passwordStrengthCheck\": true,\n  \"maxUploadSizeMb\": 500,\n  \"maxDownloadSizeMb\": 500,\n  \"storage\": {\n    \"/\": {\n      \"accept\": [\"store\"]\n    },\n    \"/bropdox\": {\n      \"accept\": [\"store\", \"delete\"],\n      \"expireSeconds\": 86400\n    },\n    \"/archive\": {\n      \"accept\": [\"delete\"]\n    }\n  }\n}\n```\n\nAll fields are optional.\nOnly specify the ones you want to override.\n\nRelative `storageDir` and `usersFile` paths are resolved from the directory containing `config.json`.\n\n---\n\n## Authentication\n\n`uplodah` also supports authentication.\n\n| Authentication Mode | Details                                                                               | Auth Initialization |\n| :------------------ | :------------------------------------------------------------------------------------ | :------------------ |\n| `none`              | Default. No authentication required                                                   | Not required        |\n| `publish`           | Authentication required for uploads and admin UI. Listing and downloads remain public | Required            |\n| `full`              | Authentication required for all operations (must login first)                         | Required            |\n\nTo enable authentication on `uplodah`, first register an initial user using the `--auth-init` option.\n\n### Initialize\n\nCreate an initial admin user interactively:\n\n```bash\nuplodah --auth-init\n```\n\nThis command will:\n\n1. Prompt for admin username\n2. Prompt for password (with strength checking, masked input)\n3. Create `users.json`\n4. Exit after initialization (server does not start)\n\nWhen enabling authentication with the Docker image, run this option against the same mounted config/data directory so that `users.json` is created in persistent storage.\n\n### Example session\n\n```\nInitializing authentication...\nEnter admin username: admin\nEnter password: ********\nPassword strength: Good\nConfirm password: ********\nCreating admin user...\n\n============================================================\nAdmin user created successfully!\n============================================================\nUsername: admin\nRole: admin\n============================================================\n\nNote: You need to generate an API password for API access.\nYou can do this through the web UI after logging in with your username and password.\n============================================================\n```\n\n### User Management\n\nUsers added with `--auth-init` automatically become administrator users.\nAdministrator users can add or remove other users through the UI, and can reset user passwords.\n\nAvailable roles are:\n\n- `read`: browse, list, and download files\n- `publish`: same as `read`, plus upload files\n- `admin`: same as `publish`, plus user management\n\nAdministrator users can also generate API passwords, but it is usually better to separate day-to-day upload accounts from the admin account.\n\n### Using API passwords\n\n`uplodah` distinguishes between the password used to log in to the UI and the password used by API clients.\nAPI clients use an \"API password\" with HTTP Basic authentication.\n\nLog in through the browser UI first, then open the API password screen from the user menu and create one or more labeled API passwords.\nThe plaintext API password is shown only once, so store it securely.\n\nExamples:\n\n```bash\n# Upload with API password\ncurl -X POST http://localhost:5968/api/upload/report.txt \\\n  -u publisher:xxxxxxxxxxxxxxxxxxxxxx \\\n  -H \"Content-Type: application/octet-stream\" \\\n  --data-binary @./report.txt\n```\n\n```bash\n# List files with API password (required in authMode=full)\ncurl \"http://localhost:5968/api/files?skip=0\u0026take=20\" \\\n  -u reader:xxxxxxxxxxxxxxxxxxxxxx\n```\n\n```bash\n# Download the latest file version with API password (required in authMode=full)\ncurl -L \"http://localhost:5968/api/files/report.txt\" \\\n  -u reader:xxxxxxxxxxxxxxxxxxxxxx \\\n  -o ./report.txt\n```\n\nIn `publish` mode, HTTP Basic authentication is required only for upload APIs.\nIn `full` mode, provide Basic authentication for all API routes, while the browser UI uses the session created after login.\n\n### Password strength requirements\n\n`uplodah` uses the `zxcvbn` library to enforce strong password requirements:\n\n- Evaluates password strength on a scale of 0-4 (Weak to Very Strong)\n- Default minimum score: 2 (Good)\n- Checks against common passwords, dictionary words, and patterns\n- Provides feedback during password creation\n\nConfigure password requirements in `config.json`:\n\n```json\n{\n  \"passwordMinScore\": 2, // 0-4, default: 2 (Good)\n  \"passwordStrengthCheck\": true // default: true\n}\n```\n\n`uplodah` stores both login passwords and API passwords as salted hashes, so plaintext passwords are not saved on disk.\nHowever, if you do not use HTTPS (TLS), the `Authorization` header contains the plaintext API password, which makes it vulnerable to sniffing.\nIf the server is exposed beyond a trusted local network, protect communications with HTTPS.\n\n---\n\n## Reverse Proxy Interoperability\n\nThe server is designed to run behind a reverse proxy.\nFor example, you may want to expose it at `https://files.example.com/uplodah` while the Node.js server itself runs on another host or port internally.\n\n### URL Resolution\n\nThe server resolves its public URL with the following priority:\n\n1. Fixed base URL: `--base-url` or `baseUrl`\n2. `Forwarded` header\n3. `X-Forwarded-Proto`, `X-Forwarded-Host`, and `X-Forwarded-Port`\n4. Normal `Host` header\n\nFor subpath hosting, the path prefix can be resolved in one of these ways:\n\n- Include the path in `baseUrl`\n- Send the `X-Forwarded-Path` header\n\nThe most reliable option is to fix `baseUrl` explicitly:\n\n```bash\nuplodah --base-url https://files.example.com/uplodah\n```\n\nIn that case, the public URLs look like this:\n\n- Web UI: `https://files.example.com/uplodah/`\n- File listing API: `https://files.example.com/uplodah/api/files`\n- Download API: `https://files.example.com/uplodah/api/files/report.txt`\n\nIf you want to explicitly define trusted proxies, configure `trustedProxies`:\n\n```bash\nuplodah --trusted-proxies \"10.0.0.10,10.0.0.11\"\n```\n\nYou can provide the same values via environment variables:\n\n```bash\nexport UPLODAH_BASE_URL=https://files.example.com/uplodah\nexport UPLODAH_TRUSTED_PROXIES=10.0.0.10,10.0.0.11\nexport UPLODAH_CONFIG_FILE=/srv/uplodah/config.json\nexport UPLODAH_STORAGE_DIR=/srv/uplodah/storage\nexport UPLODAH_MAX_UPLOAD_SIZE_MB=500\n```\n\n---\n\n## Docker usage\n\nDocker images are available for multiple architectures:\n\n- `linux/amd64` (x86_64)\n- `linux/arm64` (aarch64)\n\nWhen pulling the image, Docker automatically selects the appropriate architecture for your platform.\n\n### Quick start\n\nSuppose you have configured the following directory structure for persistence (recommended):\n\n```\ndocker-instance/\n├── data/\n│   └── config.json\n└── storage/\n    └── (uploaded files)\n```\n\nExecute as follows:\n\n```bash\n# Pull and run the latest version\ndocker run -d -p 5968:5968 \\\n  -v $(pwd)/data:/data \\\n  -v $(pwd)/storage:/storage \\\n  kekyo/uplodah:latest\n\n# Or with Docker Compose\ncat \u003e docker-compose.yml \u003c\u003c EOF\nversion: '3'\nservices:\n  uplodah:\n    image: kekyo/uplodah:latest\n    ports:\n      - \"5968:5968\"\n    volumes:\n      - ./data:/data\n      - ./storage:/storage\n    environment:\n      - UPLODAH_BASE_URL=http://localhost:5968\nEOF\n\ndocker-compose up -d\n```\n\n`uplodah` is now available at:\n\n- Web UI: `http://localhost:5968/`\n- File listing API: `http://localhost:5968/api/files`\n- Upload API: `http://localhost:5968/api/upload/\u003cfile-name\u003e`\n- Download API: `http://localhost:5968/api/files/\u003cfile-name\u003e`\n- Health check: `http://localhost:5968/health`\n\n### Permission requirements\n\nThe Docker container runs as the `uplodah` user (UID 1001) for security reasons.\nYou need to ensure that the mounted directories have the appropriate permissions for this user to read and write files.\n\n**Set proper permissions for mounted directories:**\n\n```bash\n# Create directories if they don't exist\nmkdir -p ./data ./storage\n\n# Set ownership to UID 1001 (matches the container's uplodah user)\nsudo chown -R 1001:1001 ./data ./storage\n```\n\n**Important**: Without proper permissions, you may encounter `Permission denied` errors when:\n\n- Creating directories for uploads\n- Writing uploaded files into `/storage`\n- Deleting expired files from `/storage`\n- Reading `/data/config.json`\n\n### Basic usage\n\n```bash\n# Run with default settings (port 5968, storage and config taken from mounted volumes)\ndocker run -p 5968:5968 \\\n  -v $(pwd)/data:/data \\\n  -v $(pwd)/storage:/storage \\\n  kekyo/uplodah:latest\n\n# With a fixed public base URL\ndocker run -p 5968:5968 \\\n  -v $(pwd)/data:/data \\\n  -v $(pwd)/storage:/storage \\\n  -e UPLODAH_BASE_URL=https://files.example.com/uplodah \\\n  kekyo/uplodah:latest\n```\n\nYou can also change settings using environment variables or command-line options, but the easiest way to configure settings is to use `config.json`.\n\nSince the Docker image has mount points configured, you can mount `/data` and `/storage` as shown in the example above and place `/data/config.json` there to flexibly configure settings.\nBelow is an example of `config.json`:\n\n```json\n{\n  \"port\": 5968,\n  \"baseUrl\": \"http://localhost:5968\",\n  \"realm\": \"Awesome uplodah\",\n  \"logLevel\": \"info\",\n  \"maxUploadSizeMb\": 500,\n  \"maxDownloadSizeMb\": 500,\n  \"storage\": {\n    \"/\": {\n      \"accept\": [\"store\"]\n    },\n    \"/bropdox\": {\n      \"accept\": [\"store\", \"delete\"],\n      \"expireSeconds\": 86400\n    },\n    \"/archive\": {\n      \"accept\": [\"delete\"]\n    }\n  }\n}\n```\n\nNote: The default container command already specifies `--config-file /data/config.json --storage-dir /storage`.\nIf you need a different storage directory or config file path, override the container command explicitly.\n\n### Volume mounts and configuration\n\n- `/data`: Default location for `config.json` and other runtime files you want to place beside it\n- `/storage`: Default upload storage directory\n\n**Default behavior**: The Docker image runs with `--config-file /data/config.json --storage-dir /storage` by default.\n\n**Configuration priority** (highest to lowest):\n\n1. Custom command line arguments (when overriding CMD)\n2. Environment variables for settings not already fixed by the command line, such as `UPLODAH_BASE_URL`\n3. `config.json` values loaded from `/data/config.json`\n4. Built-in default values in `uplodah`\n\n### Example of Automatic Startup Using systemd\n\nVarious methods exist for automatically starting containers with systemd.\nBelow is a simple example of configuring a systemd service using Podman.\nThis is a simple service unit file used before quadlets were introduced to Podman.\nBy placing this file and having systemd recognize it, you can automatically start `uplodah`:\n\n`/etc/systemd/system/container-uplodah.service`:\n\n```ini\n# container-uplodah.service\n\n[Unit]\nDescription=Podman container-uplodah.service\nDocumentation=man:podman-generate-systemd(1)\nWants=network-online.target\nAfter=network-online.target\nRequiresMountsFor=%t/containers\n\n[Service]\nEnvironment=PODMAN_SYSTEMD_UNIT=%n\nRestart=always\nRestartSec=30\nTimeoutStopSec=70\nExecStart=/usr/bin/podman run \\\n        --cidfile=%t/%n.ctr-id \\\n        --cgroups=no-conmon \\\n        --rm \\\n        --sdnotify=conmon \\\n        --replace \\\n        -d \\\n        -p 5968:5968 \\\n        --name uplodah \\\n        -v /export/data:/data -v /export/storage:/storage docker.io/kekyo/uplodah:latest\nExecStop=/usr/bin/podman stop \\\n        --ignore -t 10 \\\n        --cidfile=%t/%n.ctr-id\nExecStopPost=/usr/bin/podman rm \\\n        -f \\\n        --ignore -t 10 \\\n        --cidfile=%t/%n.ctr-id\nType=notify\nNotifyAccess=all\n\n[Install]\nWantedBy=default.target\n```\n\n---\n\n## Building the Docker image (Advanced)\n\nThe build of the `uplodah` Docker image uses Podman.\n\n### Multi-platform build with Podman (recommended)\n\nUse the provided multi-platform build script that uses Podman to build for all supported architectures:\n\n```bash\n# Build for all platforms (local only, no push)\n./build-docker-multiplatform.sh\n\n# Build and push to Docker Hub\n./build-docker-multiplatform.sh --push\n\n# Build for specific platforms only\n./build-docker-multiplatform.sh --platforms linux/amd64,linux/arm64\n\n# Push with custom Docker Hub username\nOCI_SERVER_USER=yourusername ./build-docker-multiplatform.sh --push\n\n# Inspect existing manifest\n./build-docker-multiplatform.sh --inspect\n```\n\n**Important**: For cross-platform builds, QEMU emulation must be configured first:\n\n```bash\n# Option 1: Use QEMU container (recommended)\nsudo podman run --rm --privileged docker.io/multiarch/qemu-user-static --reset -p yes\n\n# Option 2: Install system packages\n# Ubuntu/Debian:\nsudo apt-get update \u0026\u0026 sudo apt-get install -y qemu-user-static\n# Fedora/RHEL:\nsudo dnf install -y qemu-user-static\n\n# Verify QEMU is working:\npodman run --rm --platform linux/arm64 alpine:latest uname -m\n# Should output: aarch64\n```\n\nWithout QEMU, you can only build for your native architecture.\n\n---\n\n## Notes\n\n### Health Check\n\n`/health` returns a response like this:\n\n```json\n{\n  \"status\": \"ok\",\n  \"version\": \"0.1.0\"\n}\n```\n\n### Configuration Reference Table\n\nAll settings are resolved with the priority **CLI \u003e environment variable \u003e config.json \u003e default**.\n\n| CLI option                      | Environment variable                 | `config.json` key       | Description                                              | Valid values                               | Default             |\n| :------------------------------ | :----------------------------------- | :---------------------- | :------------------------------------------------------- | :----------------------------------------- | :------------------ |\n| `-p, --port \u003cport\u003e`             | `UPLODAH_PORT`                       | `port`                  | HTTP server listening port                               | 1-65535                                    | `5968`              |\n| `-b, --base-url \u003curl\u003e`          | `UPLODAH_BASE_URL`                   | `baseUrl`               | Fixed external base URL                                  | valid URL                                  | auto-detected       |\n| `-d, --storage-dir \u003cdir\u003e`       | `UPLODAH_STORAGE_DIR`                | `storageDir`            | Storage root directory                                   | valid path                                 | `./storage`         |\n| `-c, --config-file \u003cpath\u003e`      | `UPLODAH_CONFIG_FILE`                | N/A                     | Path to the configuration file                           | valid path                                 | `./config.json`     |\n| `-u, --users-file \u003cpath\u003e`       | `UPLODAH_USERS_FILE`                 | `usersFile`             | Path to the users database file                          | valid path                                 | `./users.json`      |\n| `-r, --realm \u003crealm\u003e`           | `UPLODAH_REALM`                      | `realm`                 | UI title and server label                                | string                                     | `uplodah [version]` |\n| `-l, --log-level \u003clevel\u003e`       | `UPLODAH_LOG_LEVEL`                  | `logLevel`              | Log verbosity                                            | `debug`, `info`, `warn`, `error`, `ignore` | `info`              |\n| `--trusted-proxies \u003cips\u003e`       | `UPLODAH_TRUSTED_PROXIES`            | `trustedProxies`        | Comma-separated trusted proxy IP list                    | list of IP addresses                       | none                |\n| `--auth-mode \u003cmode\u003e`            | `UPLODAH_AUTH_MODE`                  | `authMode`              | Authentication mode                                      | `none`, `publish`, `full`                  | `none`              |\n| N/A                             | `UPLODAH_SESSION_SECRET`             | `sessionSecret`         | Secret used for session cookies                          | string                                     | auto-generated      |\n| N/A                             | `UPLODAH_PASSWORD_MIN_SCORE`         | `passwordMinScore`      | Minimum password strength score                          | 0-4                                        | `2`                 |\n| N/A                             | `UPLODAH_PASSWORD_STRENGTH_CHECK`    | `passwordStrengthCheck` | Enable password strength checking                        | `true`, `false`                            | `true`              |\n| `--max-upload-size-mb \u003csize\u003e`   | `UPLODAH_MAX_UPLOAD_SIZE_MB`         | `maxUploadSizeMb`       | Maximum upload size in MB                                | 1-10000                                    | `100`               |\n| `--max-download-size-mb \u003csize\u003e` | `UPLODAH_MAX_DOWNLOAD_SIZE_MB`       | `maxDownloadSizeMb`     | Maximum selected batch download size in MB               | 1-10000                                    | `100`               |\n| N/A                             | N/A                                  | `storage`               | Per-virtual-directory storage policy                     | object                                     | unset               |\n| N/A                             | `UPLODAH_AUTH_FAILURE_DELAY_ENABLED` | N/A                     | Enable progressive delays for failed auth attempts       | `true`, `false`                            | `true`              |\n| N/A                             | `UPLODAH_AUTH_FAILURE_MAX_DELAY`     | N/A                     | Maximum delay for failed auth attempts (ms)              | number                                     | `10000`             |\n| `--auth-init`                   | N/A                                  | N/A                     | Initialize authentication with an interactive admin user | flag                                       | N/A                 |\n\n## Other\n\nThis server project is a sister project of [nuget-server](https://github.com/kekyo/nuget-server/).\n\n## License\n\nUnder MIT.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkekyo%2Fuplodah","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkekyo%2Fuplodah","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkekyo%2Fuplodah/lists"}