NizenVault is a lightweight, self-hosted image manager for S3-compatible storage. Upload, organize, and serve images with workspaces, metadata editing, and a modern UI — powered by PHP, SQLite, and Docker.
- 📁 Workspace Management — Organize images into workspaces (e.g., per article, per project).
- ☁️ S3-Compatible Storage — Works with AWS S3, DigitalOcean Spaces, Cloudflare R2, Wasabi, etc.
- 🖼️ Gallery View — Browse images with skeleton loaders, lazy loading, and smooth View Transitions.
- 🏷️ Metadata Editing — Update alt text, captions, and sort order per image.
- ⚙️ Background Worker — Handles heavy operations (rename/delete workspace with many assets) asynchronously.
- 🛡️ Security — CSRF protection, brute-force rate limiting, secure session management.
- 🎨 Dynamic Island — Real-time floating status pill for background job activity.
- 🌗 Dark/Light Mode — Fully responsive with theme toggle.
Request → Nginx → public/index.php (Front Controller)
↓
Router.php
↓
DI Container.php
↓ ↓
Controllers Services
↓ ↓
SQLite DB S3 Storage
Stack: PHP 8.4 (FPM) · SQLite · Nginx · Docker (3 services: app, web, worker)
# 1. Clone the repository
git clone https://github.com/mbahnizen/nizenvault.git
cd nizenvault
# 2. Configure environment
cp .env.example .env
# Edit .env — set ADMIN_PASSWORD and S3 credentials
# 3. Start the stack
docker compose up -d
# 4. Open in browser
# http://localhost:8000After cloning and configuring .env, the docker-compose.override.yml automatically mounts ./public and ./src into the container for live-reload — no rebuild needed for PHP/JS/CSS changes.
All API endpoints require authentication (session cookie) unless noted. Mutating endpoints also require the X-CSRF-TOKEN header.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/login |
✗ | Login with admin password |
GET |
/api/health |
✓ | System health check |
curl -X POST http://localhost:8000/api/login \
-H "Content-Type: application/json" \
-d '{"password":"your_admin_password"}'| Method | Endpoint | Description |
|---|---|---|
GET |
/api/list_assets?workspace_id=1 |
List assets in workspace (paginated) |
POST |
/api/upload |
Upload image to workspace |
POST |
/api/delete |
Soft-delete an asset |
POST |
/api/rename_asset |
Rename an asset (copies S3 object) |
POST |
/api/update_asset |
Update metadata (alt, caption, sort) |
POST |
/api/cleanup_deleted |
Purge soft-deleted assets older than N days |
curl -X POST http://localhost:8000/api/upload \
-H "X-CSRF-TOKEN: {token}" \
-b "PHPSESSID={session}" \
-F "file=@/path/to/image.jpg" \
-F "workspace_id=1" \
-F "filename=my-photo"curl http://localhost:8000/api/list_assets?workspace_id=1&page=1&limit=50 \
-b "PHPSESSID={session}"| Method | Endpoint | Description |
|---|---|---|
GET |
/api/workspaces |
List all workspaces |
POST |
/api/workspaces |
Create a new workspace |
POST |
/api/rename_workspace |
Rename workspace (may queue background job) |
POST |
/api/delete_workspace |
Delete workspace and its assets |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/workspace_jobs |
List all jobs |
GET |
/api/workspace_jobs_active |
List running/queued jobs |
GET |
/api/workspace_job_status?id=1 |
Get job status by ID |
POST |
/api/workspace_job_cancel |
Cancel a queued job |
POST |
/api/workspace_job_retry |
Retry a failed job |
POST |
/api/workspace_job_force_reset |
Force-reset a stale running job |
| Variable | Description | Default |
|---|---|---|
| App | ||
APP_ENV |
Environment mode | production |
APP_DEBUG |
Enable debug output | false |
LOG_CHANNEL |
Log target: stderr or file |
file |
| S3 Storage | ||
S3_ENDPOINT |
S3 API endpoint URL | Required |
S3_BUCKET |
Bucket name | Required |
S3_ACCESS_KEY |
Access key ID | Required |
S3_SECRET_KEY |
Secret access key | Required |
S3_REGION |
S3 region | id-jkt-1 |
S3_ACL |
Object ACL | public-read |
PUBLIC_URL_BASE |
Public base URL for assets | Required |
| Auth & Session | ||
ADMIN_PASSWORD |
Admin login password | Required |
SESSION_LIFETIME_SECONDS |
Session cookie lifetime | 43200 (12h) |
SESSION_IDLE_TIMEOUT_SECONDS |
Idle timeout before auto-logout | 7200 (2h) |
| Worker | ||
WORKSPACE_SYNC_THRESHOLD |
Max assets before queuing background job | 20 |
WORKSPACE_JOB_STALE_MINUTES |
Minutes before a running job is stale | 15 |
WORKER_SLEEP_SECONDS |
Worker polling interval | 2 |
nizenvault/
├── public/ # Web root (only directory exposed by Nginx)
│ ├── index.php # Front controller + SPA HTML
│ ├── app.js # Client-side logic
│ └── app.css # Styles
├── src/ # PHP source (PSR-4: App\)
│ ├── Controller/ # Route handlers
│ ├── Router.php # URL routing
│ ├── Container.php # Dependency injection
│ ├── Database.php # SQLite connection + schema
│ ├── StorageService.php # S3 operations
│ └── bootstrap.php # App initialization
├── scripts/ # CLI scripts
│ └── workspace_worker.php # Background job processor
├── docker/ # Docker support files
│ ├── nginx/default.conf
│ └── entrypoint.sh
├── docker-compose.yml # Production stack
├── docker-compose.override.yml # Dev overrides
├── Dockerfile
└── .env.example
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
MIT License — see LICENSE for details.