Photon is an asynchronous, distributed, fault-tolerant image processing pipeline built as a production-style portfolio project.
It accepts browser uploads, stores original images in object storage, creates durable jobs, pushes work through Redis, processes images in separate worker containers, stores generated outputs, exposes metrics, and deploys to a real VPS-backed `k3s` cluster through CI/CD.
The project is intentionally more than a CRUD demo. It is designed to show practical backend, infrastructure, and operations skills across Go, Redis, PostgreSQL, Docker, Kubernetes, GitHub Actions, object storage, observability, and VPS administration.
Photon turns uploaded images into generated variants without making the user wait on the request path.
- The API returns a presigned upload URL for direct browser-to-MinIO upload.
- The client uploads the source image to object storage.
- The API creates a job record in PostgreSQL and enqueues the job ID in Redis.
- Worker processes claim jobs asynchronously, download source objects, run ImageMagick transforms, and upload generated outputs.
- The API exposes job status and presigned download URLs for completed outputs.
- A cleanup service expires old jobs, uploaded sources, generated outputs, attempts, and dead-letter records.
Supported output formats include jpg, png, webp, and avif. Jobs can request multiple named variants with width, height, and quality settings.
Photon is built to demonstrate the kind of engineering that sits between application code and real operations:
- asynchronous job orchestration instead of synchronous request blocking
- distributed API and worker services connected through Redis
- durable job state, attempts, retries, and output metadata in PostgreSQL
- object-storage based upload and download flow using presigned URLs
- automatic retry and dead-letter handling for failed work
- stale job recovery when a worker dies mid-attempt
- metrics, health checks, readiness checks, and structured logs
- containerized local development with Docker Compose
- Kubernetes deployment manifests for a single-node
k3scluster - CI/CD that builds immutable images, pushes to GHCR, and deploys to a VPS over SSH
- operational documentation for DNS, TLS, firewalling, SSH access, and server hardening
- Browser gets a presigned upload URL from the API.
- Browser uploads the original image directly to MinIO.
- Browser asks the API to create a job.
- API stores job metadata in PostgreSQL and enqueues the job ID in Redis.
- Worker claims the job, downloads the source image from MinIO, and generates variants.
- Worker uploads outputs to MinIO and saves output metadata in PostgreSQL.
- Browser asks the API for results.
- API returns presigned download URLs.
- Browser downloads generated outputs directly from MinIO
The API and workers are separate deployable units. Workers can be scaled independently from the API, and the queue boundary keeps image processing off the latency-sensitive request path.
Photon treats image processing as unreliable work that must be recoverable.
- Jobs move through explicit states:
queued,processing,completed,failed, anddead_lettered. - Each processing run creates a job attempt record.
- Failed attempts are retried until
PHOTON_REDIS_MAX_RETRIESis exhausted. - Exhausted jobs are pushed to a Redis dead-letter queue with failure metadata.
- Workers recover stale
processingjobs when a lease expires before completion. - The API marks jobs failed if persistence succeeds but queue enqueueing fails.
- Cleanup removes expired job history and object storage artifacts so the demo remains operational over time.
| Area | Tools |
|---|---|
| Backend services | Go, standard net/http, pgx, go-redis, MinIO SDK |
| Image processing | ImageMagick inside the worker image |
| Queueing | Redis queue plus dead-letter queue |
| Persistence | PostgreSQL migrations and job metadata |
| Object storage | MinIO with presigned upload and download URLs |
| Frontend | React, TypeScript, Vite, Tailwind, embedded into the Go API binary |
| Local runtime | Docker Compose |
| Deployment | Docker, GHCR, Kubernetes manifests, k3s, Traefik |
| Observability | Prometheus metrics, Grafana manifests, health and readiness probes, structured JSON logs |
| CI/CD | GitHub Actions for formatting, tests, frontend build, image publishing, and VPS deployment |
| Operations | Ubuntu VPS, SSH deploy user, firewall hardening, DNS, HTTPS via Let's Encrypt |
The core implementation is Go. The worker boundary is intentionally service-oriented: additional processors can be added later by consuming the same Redis job contract and persisting results through the same database/storage model.
The API serves both the browser UI and the JSON workflow endpoints.
| Endpoint | Purpose |
|---|---|
GET / |
Embedded browser UI |
POST /v1/uploads/presign |
Create a presigned source upload URL |
POST /v1/jobs |
Create and enqueue an image processing job |
GET /v1/jobs/{id} |
Read job status and metadata |
GET /v1/jobs/{id}/results |
Read output metadata and presigned download URLs |
POST /v1/jobs/{id}/retry |
Retry a failed or dead-lettered job |
GET /healthz |
Liveness check |
GET /readyz |
Dependency readiness check |
GET /metrics |
Prometheus metrics |
Rate limiting protects upload presign, job creation, and retry endpoints. The limiter respects forwarded client IP headers for operation behind Traefik.
Start the full stack:
docker compose -f deploy/docker/docker-compose.yml up --buildDocker Compose starts:
- PostgreSQL
- Redis
- MinIO
- bucket bootstrapper
- migration job
- API
- worker
- cleanup runner
Local URLs:
| Service | URL |
|---|---|
| Photon UI/API | http://localhost:18080 |
| Worker metrics | http://localhost:18081/metrics |
| MinIO API | http://localhost:9000 |
| MinIO console | http://localhost:9001 |
Run the Go services outside Docker after starting infrastructure:
go run ./cmd/migrate up
go run ./cmd/api
go run ./cmd/worker
go run ./cmd/cleanup serveRun tests:
go test ./...Build the frontend:
cd frontend
npm ci
npm run buildPhoton includes a deployable Kubernetes stack in deploy/k8s for a single-node k3s cluster.
The current deployment model uses:
- GitHub Actions
- GHCR image publishing
- SSH to a plain Ubuntu VPS
scripts/deploy-k8s.sh- Kubernetes secrets created from GitHub repository secrets
- Traefik ingress with Let's Encrypt HTTP-01 certificates
- Prometheus and Grafana manifests for observability
Public hostnames used by the current deployment plan:
photon.abhinash.devfor the app/APIstorage.photon.abhinash.devfor MinIO object accessminio.photon.abhinash.devfor the MinIO consolegrafana.photon.abhinash.devfor Grafana
The deploy workflow builds and pushes four images:
photon-apiphoton-workerphoton-migratephoton-cleanup
Then it uploads a deployment bundle to the VPS and applies the Kubernetes manifests with immutable image tags.
cmd/
api/ HTTP API and embedded frontend server
worker/ asynchronous image processing worker
cleanup/ retention and object cleanup process
migrate/ database migration entry point
deploy/
docker/ Dockerfiles and local Docker Compose stack
k8s/ k3s-oriented Kubernetes manifests
docs/ build, deployment, CI/CD, and operations notes
frontend/ React/TypeScript product UI
internal/
api/ HTTP handlers, static embedding, rate limiting
cleanup/ retention runner
config/ environment-driven configuration
db/ PostgreSQL repositories and models
imageproc/ transform validation and ImageMagick execution
observability/
platform/ logging, migrations, postgres connection helpers
queue/ Redis queue and DLQ implementation
storage/ MinIO client and presigned URL handling
worker/ worker runtime and retry logic
migrations/ SQL schema migrations
scripts/ local and deployment helpers
Photon is a compact project, but it exercises real production concerns:
- Go service design with clean package boundaries
- asynchronous distributed processing with Redis
- fault tolerance through retries, attempts, DLQ, and stale job recovery
- PostgreSQL schema design and migration flow
- MinIO/S3-style object storage integration
- Docker image design for multiple service roles
- Kubernetes deployment on
k3s - CI/CD with GitHub Actions and GHCR
- VPS operations, DNS, TLS, SSH hardening, and firewall setup
- frontend integration with a backend-driven upload pipeline
- a clear extension path for additional worker types or image analysis stages
