No description
- Go 43.2%
- CSS 29.4%
- HTML 26.5%
- Dockerfile 0.9%
| .github/workflows | ||
| static | ||
| templates | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| image.go | ||
| main.go | ||
| README.md | ||
| store.go | ||
picgo
A self-hosted image hosting web app written in Go. Upload, browse, and delete pictures from a private dashboard with automatic thumbnail generation.
Features
- Single-admin login with rate-limited brute-force protection (5 attempts, 15-minute lockout)
- Upload JPEG, PNG, GIF, and WebP images up to 32 MB
- Automatic thumbnail generation with EXIF orientation correction
- Paginated dashboard (20 images per page)
- SQLite-backed metadata store (WAL mode)
- CSRF protection on all mutating actions
- 1-year cache headers on served images
- Structured request logging via
slog
Running locally
go run .
The server starts on port 8080. Visit http://localhost:8080 and log in with the default credentials (admin / password123).
Data is stored under ./data/ — uploaded files in data/uploads/, thumbnails in data/uploads/thumbs/, and the SQLite database at data/picgo.db.
Configuration
All configuration is via environment variables:
| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
HTTP port to listen on |
DATA_DIR |
data |
Directory for the database and uploaded files |
ADMIN_USERNAME |
admin |
Admin login username |
ADMIN_PASSWORD |
password123 |
Admin login password — change this |
SECURE_COOKIES |
false |
Set to true to mark session cookies as Secure (required behind HTTPS) |
Docker
Build and run with Docker:
docker build -t picgo .
docker run -p 8080:8080 -v $(pwd)/data:/app/data \
-e ADMIN_PASSWORD=yourpassword \
picgo
Or with Docker Compose:
services:
picgo:
image: ghcr.io/your-org/picgo:latest
ports:
- "8080:8080"
volumes:
- ./data:/app/data
environment:
ADMIN_PASSWORD: yourpassword
SECURE_COOKIES: "true"
restart: unless-stopped
CI / CD
GitHub Actions runs on every push and pull request to main:
- Build —
go build ./...andgo vet ./... - Docker — builds and pushes the image to GHCR (
ghcr.io/<owner>/<repo>:latestand:<sha>) on pushes tomain - Deploy — SSHes into a VPS, pulls the new image, and restarts via
docker compose
Required repository secrets for deployment: VPS_HOST, VPS_USER, VPS_SSH_KEY, VPS_PORT, GHCR_TOKEN, GHCR_USER.
Routes
| Method | Path | Description |
|---|---|---|
| GET | / |
Redirects to dashboard or login |
| GET | /login |
Login page |
| POST | /login |
Authenticate |
| POST | /logout |
Invalidate session |
| GET | /dashboard |
Image management dashboard |
| POST | /upload |
Upload an image |
| POST | /delete/{id} |
Delete an image |
| GET | /pics/{filename} |
Serve the original image |
| GET | /thumb/{filename} |
Serve the 400×400 JPEG thumbnail |
Tech stack
- Go with chi router
- SQLite via modernc.org/sqlite (no CGO)
- imaging for thumbnail generation
- goexif for EXIF orientation correction