As many already know Ecency running our own instance of imagehoster for long time (6+ years) and we have made many improvements and iterations of software over the course of last few years. Our modifications became so messy that it was kept on private repo until recently. We decided to push our changes into public repo and do some cleanup https://github.com/ecency/imagehoster so community can check our changes and improvements.

This is a comprehensive modernization overview of the Ecency image hosting and proxying service - from a 2017-era codebase to a production-hardened, performant system. Over the years our changes and instances were focused around serving webp image format as priority. As it would help website and app become faster to load due to superior image compression and size, that has changed in last couple years as technology improved we saw switch to avif image compression. So our current instance prioritize avif image compression then fallback to webp and then serve original or legacy format. Entire Ecency mobile app and website usages were using dedicated path for /webp/, and doing content negotiations on app side, we have migrated away from webp dedicated path to better content negotiation directly from imagehoster. Which improved performance across the board and became scalable and easier to maintain.
Codebase is much more modern and utilizing latest library versions. Below tables show differences from old imagehoster codebase to new one.
At a Glance
Area | Before | After | Impact |
|---|---|---|---|
Node.js | 8+ (Alpine) | 20 (Bookworm) | Modern APIs, better performance |
TypeScript | 3.9 | 5.7 | Stronger types, faster compilation |
Linter | TSLint (deprecated) | ESLint | Maintained, better rules |
Sharp | 0.32 | 0.33.5 | Bundled libvips, AVIF/HEIC support |
AWS SDK | v2 (monolithic, 60MB+) | v3 (modular, ~3MB) | Smaller builds, tree-shakeable |
Redis client | v2 (callbacks) | v4 (async/await) | Clean async code |
| 0.14 | 1.3.4 | Stable API, better types |
Docker image | Alpine + manual vips build | Debian slim, Sharp bundles vips | Simpler, more reliable |
Key Improvements
Storage: Custom S3 Store (replaces s3-blob-store)
Feature | Old (s3-blob-store + aws-sdk v2) | New (custom S3BlobStore + SDK v3) |
|---|---|---|
Upload method | Stream-only | Direct |
Write stream | Double-buffered in memory | Streams directly to S3 |
Prefix deletion | Not supported | Native |
Bundle size | ~60MB (full aws-sdk) | ~3MB ( |
Error handling | Generic errors | Maps S3 404 → NotFound correctly |
Image Format Support
Format | Before | After |
|---|---|---|
JPEG, PNG, GIF, WebP | Yes | Yes |
SVG | Yes | Yes |
AVIF | No | Yes (encode + decode) |
HEIC/HEIF | No | Accepted (decode limited by Sharp) |
APNG | No | Yes (animation preserved) |
BMP | No | Yes |
Content negotiation | Partial | Auto via Accept header (AVIF > WebP > original) |
Rate Limiting
Aspect | Before | After |
|---|---|---|
Library |
| Custom sliding-window (Redis sorted sets) |
API style | Callbacks | async/await |
Precision | Second-level | Microsecond-level |
Window type | Fixed window | Sliding window (fairer) |
Weekly limit | 300 uploads | 700 uploads |
Max upload size | 15MB | 30MB |
RPC Failover
Aspect | Before | After |
|---|---|---|
Endpoints | 1 ( | 6 endpoints with auto-failover |
Timeout | None configured | 2000ms per request |
Profile caching | None | 30s TTL (node-cache) |
Profile API |
|
|
Cache Invalidation
Aspect | Before | After |
|---|---|---|
Invalidation | Not supported |
|
Cache bypass | Not supported |
|
CDN purge | Not supported | Cloudflare API integration |
Stale detection | None | Auto-detects corrupt/non-image cache entries |
File deletion | N/A | Direct key deletion (no directory scan) |
Avatar invalidation | N/A | Enumerates 12 known variants (4 sizes × 3 formats) |
Cover invalidation | N/A | Enumerates 3 known variants |
Fallback System
Aspect | Before | After |
|---|---|---|
Mirror sources | 1 (steemitimages.com) | 5 mirrors + default fallback |
Retry logic | None | Sequential with 5s timeout each |
Corrupt cache | Served as-is | Detected, purged, re-fetched |
Fallback caching | None | Cached under both fallback key and image key |
URL migrations | None | Automatic (3speak, InLeo, esteem.ws) |
Security
Feature | Before | After |
|---|---|---|
SSRF protection | None | Blocks private IPs, IPv6 loopback, link-local, ULA |
IPv4-mapped IPv6 | Not checked | Detected and blocked (::ffff:127.0.0.1) |
Blacklists | Static JSON arrays | Dynamic remote fetch with TTL + local fallback |
URL validation | Basic | Protocol check (http/https only) |
Invalidation auth | N/A | Token-protected header |
Docker
Aspect | Before | After |
|---|---|---|
Base | Alpine (manual vips build from edge repos) | Debian Bookworm slim |
Sharp setup | System libvips required | Sharp bundles own libvips - zero system deps |
Runtime packages | vips, heif, aom libraries | wget only (for healthcheck) |
Healthcheck | None | Built-in, respects |
Build reliability | Fragile (Alpine edge repo dependencies) | Stable (npm handles native modules) |
Performance Optimizations
Optimization | Detail |
|---|---|
Direct buffer upload |
|
No directory scanning | Invalidation deletes known keys directly instead of scanning millions of files |
Background deletion | CDN purge fires immediately, file cleanup runs async |
Streaming directory reads |
|
ETag support | 304 responses save bandwidth for repeat requests |
Fallback caching | Failed images cache the fallback under the specific key, preventing repeated re-processing |
Animation detection | Checks |
Code Quality
Metric | Before | After |
|---|---|---|
Source files | ~15 | ~22 (better separation) |
Test files | ~5 | ~10 (expanded coverage) |
Type safety | Minimal (noImplicitAny: false) | Type guards, explicit interfaces |
Error handling | Basic try/catch | Structured errors with codes and metadata |
Logging | Basic bunyan | Structured with request tracking, context tags |
New modules | — | s3-store, fallback, fetch-image, constants, blacklist-service, image-resizer, cache |
Some of the changes are focused towards improving our maintenance. For example, DMCA requests that we receive are unified inside website codebase and served to imagehoster as json file so redeployment of imagehoster becomes unnecessary, simple change to website codebase handles DMCA requests. Local and cloudflare cache cleanup also easy, previous we used to run separate script https://github.com/ecency/cf-cache to clean up cache manually, now that's handled automatically.
Overall codebase is much easier to maintain and uses best approach and improvements we added and polished over the years.
Support us
https://ecency.com/proposals?filter=team
Follow/support us on Web2 social networks
Instagram: https://www.instagram.com/ecency_official/
X/Twitter: https://x.com/ecency_official
Medium: https://ecency.medium.com
Telegram: https://t.me/ecency
Discord: https://discord.me/ecency
That's great news!
And a question: Do you support WebP animations now?
Yes, try it out and let us know. Gif and animation issues were resolved before
Nice. I'll try asap.
I just published a post https://ecency.com/hive-194913/@russia-btc/a-monkey-robbery-at-the – everything was fine in the editor, although it crashed occasionally, and after publishing, the photos didn't display at all. Everything displays fine on peakd, but there's also a problem with the main photo – check it out!
This is truly a remarkable upgrade.
Greetings
Happy to read this update
I have been using Ecency by default for month and I will keep sharing content on Hive with this front end
Peace
keep the good work up
I can upload AVIFs up to 30MB. Awesome! 👍
Going to check it out!
The HEIC/HEIF format support, the auto decoding is something I realised by accident. Used to convert offline HEIC into JPG. Now I do not have to do this anymore. How cool is that! Thanks for this extension.
Many parameters seems to be added, much of them I can just guess why they are good to have. I see mirrors, which is important, I suppose to increase uptime of the entire service.
Perhaps I should drop below somewhere else, but lets try here first:
I run ecency as an App on iPhone with latest IOS. I use Keychain app to authenticate Ecency app. Whenever I vote using Ecency, ot checks with Keychain app. Vote execute. But upon return to the Ecency app, it crashes. I reopen it again, and all good, but the crashing part is a bug I suppose. Days, a week ago, Ecency app did crash, but I had to kill it and start again because thr app itself stalled. Since a week ago and last day, I updates IOS with the latest security fix, btw.
Yes, that's natural improvement, when we see many people have heic/heif formatted images and would like easier upload, we made sure to extend our support.
In regards to iOS bug, we have fixed it new update is coming out soon.
IOS Bug: Cool!
Max upload size: is this per pic or for all pics in a post?
It is per image size, we have increased this gradually over the years when people requested and start uploading higher quality images or phone images got bigger, we have been increasing it 5MB every year or two, I don't think we will increase it further as it is big enough now without overwhelming reader's bandwidth.
Wow, that´s a lot! I always trim down my pics to <4MB in my multi-pic posts, as some would might have problems then with loading lots of large pics. But good to know that there is no limit from your end. But I really think that there is absolutely no reason to upload a 20MB pic to view on a computer screen. Only lazyness/carelessness.
Yes posts with multiple images should limit size, of course this is upload limit only when we are serving images, they are resized to more reasonable format for better reading experience
More grace to continue
!INDEED
!HUG
That's why I always love Ecency & always prefer it than otherzzzz.
Gif from Google search.....
Excellent.
!ALIVE
!BBH
!UNI
Congratulations on the updating work you have been doing over the years.
!BBH !ALIVE !HOP !PIZZA
$PIZZA slices delivered:
@pedrobrito2004(1/5) tipped @ecency
Send $PIZZA tips in Discord via tip.cc!
Excellent. This is a good overview of the ecosystem and how the system is functioning.