Deploy Thumbor
Image CDN that crops, resizes and filters photos from a URL
remotecv
Just deployed
Redis
Just deployed
/data
thumbor
Just deployed
thumbor-storage
Bucket
Just deployed
Deploy and Host Thumbor on Railway
Thumbor is an open-source imaging service that crops, resizes, filters and optimises images on demand, straight from a URL. Instead of generating every thumbnail ahead of time, you ask for /300x200/smart/ and Thumbor fetches the original, finds the faces in it, crops around them and returns that image. Wikipedia, Globo.com, Square, Forbes and Vox Media run it in front of user-generated photography, where nobody can hand-crop a million uploads and a centred crop cuts heads off.
Self-host Thumbor on Railway with the production shape already wired up. The thumbor service answers requests on a public URL; remotecv is a separate OpenCV worker that runs detection off the request path, as upstream's own documentation asks; Redis carries the detection queue and the focal points the two share; and a managed bucket holds the fetched originals and the rendered results, so no service needs a volume. Every URL is HMAC-signed, so the deployment is not an open image proxy.
![]()
Getting Started with Thumbor on Railway
Thumbor has no dashboard and no login — it is an HTTP API whose only credential is the URL-signing key. After deploying, open https:///healthcheck; it answers WORKING once the service is live. Copy the SECURITY_KEY value from the thumbor service's variables, since you need it to build any image URL. A request path is ///<source>, where the signature is a URL-safe base64 HMAC-SHA1 of everything after it. The libthumbor package builds these in Python; equivalents exist for Node, Ruby, PHP, Java and Go.
The following Python snippet signs a URL with nothing but the standard library:
import base64, hashlib, hmac
KEY = "your-security-key"
path = "300x200/smart/raw.githubusercontent.com/thumbor/thumbor/master/example.jpg"
sig = base64.urlsafe_b64encode(
hmac.new(KEY.encode(), path.encode(), hashlib.sha1).digest()
).decode()
print(f"https://your-domain/{sig}/{path}")
Open that URL and you get a 300x200 JPEG. Ask for /meta/smart/ instead of a size to see the focal points as JSON, and put /debug/ before a size to get the source with rectangles drawn on everything detected. Expect one quirk: the first /smart/ request for an unseen image returns a centred crop and queues detection, so request it again for the smart crop. Unsigned /unsafe/... URLs are rejected with 400.
![]()
About Hosting Thumbor
Thumbor serves one uploaded photo at a dozen sizes and aspect ratios with no designer in the loop. Self-host it when image URLs are part of the product, when the sources are private, or when a CDN's pricing stops adding up.
- Smart cropping driven by OpenCV face, eye and feature detection
- Resize, fit-in, flip, rotate and manual crop from the URL
- Filters for grayscale, blur, brightness, watermarking, rounded corners and format
- Automatic WebP and AVIF when the browser advertises support
- Pluggable loaders, storages, filters and detectors through a plugin API
- Signed URLs, so only holders of the key can request a transformation
The Railway architecture splits that across three services. thumbor validates the signature, fetches the source, transforms it and returns the result. remotecv takes detection jobs off a Redis queue, runs OpenCV against the source and writes the focal points back — detection is CPU-heavy, and doing it in the request blocks the server under load. Redis is both that queue and the shared store for those focal points. The bucket caches originals under st/ and renders under rs/, so a repeat request is one object read.
Why Deploy Thumbor on Railway
Railway removes the setup between you and a working imaging service:
- Thumbor, the detection worker, Redis and object storage deploy together in one click
- Managed Redis and a managed bucket, credentials wired in as references
- A public HTTPS domain with a certificate, no reverse proxy to configure
- Health checks on both the server and the worker
- Horizontal scaling for the image tier, since neither role needs a volume
Common Use Cases
- Responsive images from user uploads, where a face-aware crop beats a centred one
- A self-hosted alternative to an image CDN, in front of S3, a bucket or any public origin
- Social preview and open-graph images at fixed sizes from editorial photography
- Format negotiation, sending WebP or AVIF to browsers that accept them, JPEG to the rest
Dependencies for Thumbor
thumbor— built from gridalpha/thumbor-railway, layering the Redis, S3 and remotecv extensions ontoghcr.io/thumbor/thumbor:7-py-3.13remotecv— the same image withROLE=worker, running the remotecv OpenCV workerRedis—redis:8.2, the detection queue and shared focal-point store- Object storage — a managed bucket for source and result caching
Environment Variables Reference
| Variable | Service | Purpose |
|---|---|---|
SECURITY_KEY | thumbor | HMAC key every image URL is signed with |
ALLOW_UNSAFE_URL | thumbor | true serves unsigned URLs; leave false |
ALLOWED_SOURCES | thumbor | Hosts or regexes the loader may fetch |
DETECTION_MODE | thumbor | queued, local or none |
MAX_WIDTH / MAX_HEIGHT | thumbor | Dimension caps; 0 is unlimited |
STORAGE_EXPIRATION_SECONDS | thumbor | Cache lifetime for originals and results |
UPLOAD_ENABLED | thumbor | Upload endpoint has no auth; leave false |
DETECTOR_TIMEOUT | remotecv | Seconds one detection job may run |
Deployment Dependencies
- Source repository: gridalpha/thumbor-railway
- Upstream: thumbor/thumbor, thumbor/remotecv
- Base image:
ghcr.io/thumbor/thumbor:7-py-3.13(Python 3.13) - Docs: thumbor.readthedocs.io
Hardware Requirements for Self-Hosting Thumbor
| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 1 vCPU | 4+ vCPU (encoding is CPU-bound) |
| RAM | 512 MB | 2 GB per service |
| Storage | None on the container | A bucket for the caches |
| Runtime | Python 3.10+ with OpenCV and Pillow | Python 3.13 |
Thumbor forks one process per core, so throughput scales almost linearly with CPU. Cap MAX_WIDTH and MAX_HEIGHT if your sources are unbounded.
Self-Hosting Thumbor with Docker
The official image runs standalone, which is the fastest way to try the URL syntax locally:
docker run -p 8888:8888 ghcr.io/thumbor/thumbor:7-py-3.13 \
thumbor -p 8888 -i 0.0.0.0 -l info
That serves an unsigned instance on http://localhost:8888/unsafe/300x200/<img>. Beyond a local try, install from PyPI with the extras that pull in OpenCV, Cairo and libcurl:
pip install "thumbor[all]" remotecv tc_redis thumbor-aws
thumbor-config > thumbor.conf
thumbor -c thumbor.conf -p 8888 -l info
Set SECURITY_KEY and ALLOW_UNSAFE_URL = False there before exposing it.
How Much Does Thumbor Cost to Self-Host?
Thumbor is free and open source under the MIT licence, and so are remotecv, the Redis storage adapter and the AWS extensions this template uses. There is no paid tier, no licence key and no per-image charge. On Railway you pay only for the compute, memory, Redis and object storage used — a few dollars a month for a small site, and the cost stops scaling with the number of images served.
FAQ
What is Thumbor? An open-source imaging service that crops, resizes, filters and optimises images on demand from a URL, using OpenCV to detect faces so automatic crops keep the important part of the frame.
What does this Railway template deploy? Four pieces: the Thumbor HTTP service on a public domain, a remotecv detection worker, managed Redis for the queue and focal points, and a managed bucket for the caches.
Why does the template include Redis and object storage? Redis is how the two share work: Thumbor pushes a detection job onto it and remotecv writes the focal points back under a key Thumbor reads. Object storage holds the originals and renders, so a repeat request skips the download and the re-encode — and being shared rather than a per-container disk, it lets the image tier run several replicas.
How do I generate a signed Thumbor URL?
Take everything after the signature in the path, compute an HMAC-SHA1 of it with your SECURITY_KEY, and URL-safe base64 the digest. libthumbor does it in one call, with equivalents for Node, Ruby, PHP, Java and Go.
Why did my first smart crop come back centred?
Detection runs asynchronously. The first request for an unseen image queues the job and serves a centred crop; every later request gets the face-aware crop. Set DETECTION_MODE=local to detect inside the request and skip the worker.
Can I restrict which image sources Thumbor will fetch?
Yes. Set ALLOWED_SOURCES to a comma-separated list of hostnames or regular expressions, and only those origins load. With signed URLs, that keeps the deployment from being used as a general-purpose proxy.
Template Content
remotecv
gridalpha/thumbor-railwayRedis
redis:8.2thumbor
gridalpha/thumbor-railwaythumbor-storage
Bucket
