Bump version to 1.1.4. Add flat vs date storage paths, basename-only hide-folder URLs, and block image paths on the site host when domain separation is active.
9.8 KiB
PicHost
Lightweight personal image hosting · Simple uploads, clean management
Self-hosted / Multi-user / Docker / API / Twikoo · Local disk or object storage
Branches · Quick Start · Screenshots · Features · Tech Stack · Roadmap · 简体中文
Branches
| Branch | Notes |
|---|---|
| main (default) | v1.1.4 — local disk + multi-backend storage, bidirectional dual-domain isolation, /storage UI, hybrid URLs |
| cloudflare | Cloudflare R2–only line — streamlined for deployments that use R2 exclusively |
Use main for the full product and multiple cloud backends.
If you only use Cloudflare R2, check out the dedicated branch:
git clone https://github.com/O96u/PicHost.git
cd PicHost
git checkout cloudflare
Screenshots
| Upload | Storage |
|---|---|
![]() |
![]() |
| Stats & gallery | Settings |
|---|---|
![]() |
![]() |
Features
- Drag, click, or Ctrl+V paste to upload; admins can use custom
folderpaths - Server-side WebP (sharp), Referer hotlink protection, configurable public base URL
- Multi-user — username/password login; optional registration; users see only their images
- Upload preferences (home card flip): client-side compression, auto-copy links, per-user auto-delete
- Multi-backend storage: local disk + S3-compatible (Cloudflare R2 / Tencent COS / Alibaba OSS / AWS S3); admins manage backends at Storage (
/storage) - Hybrid URLs:
proxy(same-origin via PicHost) orpublic(302 to CDN / bucket URL) - Gallery — browse, search, batch delete; admin sees uploader badges
- Stats — upload/delete metrics, folder breakdown; admin sees registered user count
- API — global token (admin) + per-user tokens; in-app cURL snippets
- Twikoo —
POST /api/index.php(image+token) - Zero-config Docker — first-run web wizard, no secrets required upfront
Quick Start
Docker (recommended)
docker run -d \
--name pichost \
-p 6892:6892 \
-v ./data:/data \
--restart unless-stopped \
muxui/pichost:latest
Default port: 6892. Open http://<host>:6892 in your browser and complete the setup wizard.
Forgot password (requires docker exec / server access):
docker exec pichost reset-password # admin only, when there is exactly one
docker exec pichost reset-password <username> # admin or regular user
A random password is printed. Fails if the user does not exist.
If you cloned the repo, you can also use docker compose up -d (see docker-compose.yml).
Local development
npm install
cp .env.example .env # optional
npm run dev
Open http://localhost:3000/setup to create the admin account. Data defaults to ./data.
Tech Stack
| Layer | Technology |
|---|---|
| Frontend | Nuxt 4, Vue 3, Nuxt UI 4, Tailwind CSS 4 |
| Backend | Nitro (Node.js), SQLite |
| Images | sharp (WebP transcoding & compression) |
| Auth | Session cookie, scrypt password hashing, API tokens |
| Storage | Local disk + S3-compatible backends (R2 / COS / OSS / AWS); images index in SQLite |
| Deploy | Docker (amd64 / arm64), docker compose |
| Tests | Vitest |
Users & permissions
| Scenario | Auth | Ownership / visibility |
|---|---|---|
| Web upload | Session | Current user |
| API upload + user token | Auth-Token |
Token owner |
| API upload + global token | Auth-Token |
Admin (userId null) |
Twikoo /api/index.php |
form token |
Same as global token |
| Gallery list / search / delete | Session or token | Users: own only; admin: all |
Direct link GET /images/... |
None (Referer rules) | Public if URL is known |
Regular users are limited to the images/ folder; custom folder requires admin or global token.
Storage layout
/data
├── pichost.db # SQLite: users, sessions, settings, storage_backends, images index
├── images/ # Default upload folder (when using local backend)
├── blog/ # Example custom folder
└── twikoo/ # Twikoo comment images
Pattern: folder/YYYY/MM/randomId.webp + .meta.json. With cloud backends, blobs live in the bucket; SQLite images tracks backend_id and key. Back up all of /data plus bucket data.
API overview
| Method | Path | Description |
|---|---|---|
| POST | /api/images/upload |
Upload image |
| GET | /api/images |
Paginated list |
| DELETE | /api/images |
Delete one (key) |
| POST | /api/index.php |
Twikoo / EasyImage 2.0 |
Upload example:
curl -X POST "https://pic.example.com/api/images/upload" \
-H "Auth-Token: YOUR_TOKEN" \
-F "folder=blog" \
-F "image=@./demo.png"
Full docs and copyable cURL snippets are on the API page after deployment.
Twikoo
| Setting | Value |
|---|---|
IMAGE_CDN |
easyimage |
IMAGE_CDN_URL |
https://your-domain/api/index.php |
IMAGE_CDN_TOKEN |
Same as API_UPLOAD_TOKEN |
Reverse proxy
- Proxy to port 6892; HTTPS recommended
- Nginx:
client_max_body_size≥ 12m - Set
IMAGE_BASE_URL(single domain) orSITE_BASE_URL+IMAGE_BASE_URL(dual-domain separation)
Recommended architecture (dual domains)
One Docker instance + two domains, both fully proxied to 6892 (isolation via PicHost middleware):
# admin.example.com — admin + API + images
server {
server_name admin.example.com;
location / {
proxy_pass http://127.0.0.1:6892;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 12m;
}
}
# pic.example.com — same full proxy; PicHost middleware blocks non-image paths
server {
server_name pic.example.com;
location / {
proxy_pass http://127.0.0.1:6892;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
FN NAS Lucky reverse proxy (public hostname → backend http://<LAN-IP>:6892):
| Site domain (admin) | Image domain |
|---|---|
![]() |
![]() |
See docs/domain-separation.en.md for Caddy/NPM notes, env vars, and setup fields.
Development
npm run dev # Dev server
npm run build # Production build
npm run start # Run .output
npm run lint # ESLint
npm run typecheck # Type check
npm test # Unit tests
Roadmap
| Version | Plan |
|---|---|
| v1.1.4 | Flat vs date storage paths, basename public URLs, bidirectional domain isolation (current main) |
| v1.1.3 | Dual-domain separation, optional images/ URL prefix, update check |
| v1.1.2 | Activity logs, gallery storage filter, upload rate limits |
| v1.1.1 | Session check fixes, storage placeholder cleanup, Docker logging |
| v1.1.0 | Multi-backend object storage, hybrid URLs, storage UI |
| v1.0.4 | Password visibility toggle, legacy ADMIN_SECRET migration |
| v1.0.3 | CI and Docker multi-arch build fixes |
| v1.0.2 | i18n, mobile menus, stats/upload UI, Docker password reset |
| v1.0.1 | Settings version display, mobile upload preferences layout fix |
| v1.0.0 | Local storage, multi-user, API, Twikoo |






