04a078a1ad201f53169b59b1150c7b561af89fdb
Reviewed-on: #1
PNGer - Modern PNG Editor & Resizer
A simple, reactive, modern PNG editor and resizer with direct upload and download features. Built with TypeScript and deployed as a single Docker container on Unraid.
Features
- Drag & Drop Upload: Intuitive file upload interface
- Resize Operations: Width, height, and aspect ratio controls
- Crop to Fit: Smart cropping with position control (center, top, bottom, etc.)
- Format Conversion: PNG, WebP, and JPEG output
- Quality Control: Adjustable compression settings
- Direct Download: No server-side storage, immediate download
- Modern UI: Sleek, responsive TypeScript/Svelte design
Tech Stack
- Frontend: Svelte 4 + Vite + TypeScript
- Backend: Node.js + Express + TypeScript
- Image Processing: Sharp (high-performance image library)
- Container: Docker (Alpine-based, multi-stage build)
- Deployment: Unraid via Docker Compose
Quick Start
Unraid Deployment (Recommended)
-
Clone or pull this repository to your Unraid server:
cd /mnt/user/appdata git clone https://git.alwisp.com/jason/pnger.git cd pnger -
Build the Docker image:
docker build -t pnger:latest . -
Run via docker-compose:
docker compose up -d -
Access the application:
- Navigate to
http://[unraid-ip]:8080
- Navigate to
Unraid Environment Variables (Configurable via UI)
When setting up in Unraid Docker UI, you can configure:
| Variable | Default | Description |
|---|---|---|
HOST_PORT |
8080 |
External port to access the app |
MAX_FILE_SIZE |
10485760 |
Max upload size in bytes (10MB default) |
MEM_LIMIT |
512m |
Memory limit for container |
CPU_LIMIT |
1.0 |
CPU limit (1.0 = 1 full core) |
NODE_ENV |
production |
Node environment |
Unraid Docker Template Example
<?xml version="1.0"?>
<Container version="2">
<Name>pnger</Name>
<Repository>pnger:latest</Repository>
<Network>bridge</Network>
<Privileged>false</Privileged>
<WebUI>http://[IP]:[PORT:8080]</WebUI>
<Config Name="WebUI Port" Target="3000" Default="8080" Mode="tcp" Description="Port for web interface" Type="Port" Display="always" Required="true" Mask="false">8080</Config>
<Config Name="Max File Size" Target="MAX_FILE_SIZE" Default="10485760" Mode="" Description="Maximum upload file size in bytes" Type="Variable" Display="advanced" Required="false" Mask="false">10485760</Config>
<Config Name="Memory Limit" Target="" Default="512m" Mode="" Description="Container memory limit" Type="Variable" Display="advanced" Required="false" Mask="false">512m</Config>
</Container>
Local Development
Prerequisites
- Node.js 20+
- npm or yarn
Setup
-
Install backend dependencies:
cd backend npm install -
Install frontend dependencies:
cd frontend npm install -
Run development servers:
Terminal 1 (Backend):
cd backend npm run devTerminal 2 (Frontend):
cd frontend npm run dev -
Access dev server:
- Frontend:
http://localhost:5173 - Backend API:
http://localhost:3000/api
- Frontend:
Building for Production
# Backend TypeScript compilation
cd backend
npm run build
# Frontend build
cd frontend
npm run build
Docker Deployment (Manual)
# Build the image (all dependencies and builds are handled internally)
docker build -t pnger:latest .
# Run the container
docker run -d \
--name pnger \
-p 8080:3000 \
-e MAX_FILE_SIZE=10485760 \
--restart unless-stopped \
pnger:latest
Project Structure
pnger/
├── frontend/ # Svelte + TypeScript application
│ ├── src/
│ │ ├── App.svelte # Main UI component
│ │ ├── main.ts # Entry point
│ │ └── lib/
│ │ └── api.ts # API client
│ ├── package.json
│ ├── tsconfig.json
│ └── vite.config.ts
├── backend/ # Express + TypeScript API server
│ ├── src/
│ │ ├── index.ts # Express server
│ │ ├── routes/
│ │ │ └── image.ts # Image transform endpoint
│ │ └── types/
│ │ └── image.ts # TypeScript types
│ ├── package.json
│ └── tsconfig.json
├── Dockerfile # Multi-stage build (frontend + backend)
├── docker-compose.yml # Unraid deployment config
└── INSTRUCTIONS.md # Development guide
How It Works
- User uploads an image via the web interface
- Frontend sends image + transform parameters to backend API
- Backend processes image using Sharp (resize, crop, compress, convert format)
- Processed image is returned directly to browser
- Browser triggers automatic download
- No files stored on server (stateless operation)
API Endpoints
POST /api/transform
Transform an image with resize, crop, and format conversion.
Request:
- Method:
POST - Content-Type:
multipart/form-data - Body:
file: Image file (PNG/JPEG/WebP)width: Target width (optional)height: Target height (optional)quality: Quality 10-100 (optional, default: 80)format: Output formatpng|webp|jpeg(optional, default:png)fit: Resize modeinside|cover(optional, default:inside)position: Crop position whenfit=cover(optional, default:center)
Response:
- Content-Type:
image/[format] - Body: Transformed image binary
Configuration
All configuration is handled via environment variables passed through Docker/Unraid:
PORT: Server port (default:3000, internal)MAX_FILE_SIZE: Maximum upload size in bytes (default:10485760= 10MB)TEMP_DIR: Temporary directory for uploads (default:/app/temp)NODE_ENV: Node environment (default:production)
License
MIT License - See LICENSE file for details
Repository
Languages
Svelte
40.2%
TypeScript
30.2%
CSS
14.2%
JavaScript
9.8%
Dockerfile
3.8%
Other
1.8%