If you have ever wished your vacation photos looked like they came back from the dead, there is now a tiny web app for that, and it does not phone home to do it. Ruin Media is a PHP front end wrapped in a Docker container that mangles JPEGs and audio clips entirely client side. You drop a file in, choose how aggressive you want the destruction to be, and download the wreckage. Your original never leaves the browser tab.
That single fact is the reason I bothered to set it up. Most “fun” web tools that look playful on the surface are quietly uploading your camera roll to some startup’s S3 bucket. This one is different. The image and audio processing happens in JavaScript inside the page, and the Docker container is just a thin web server that hands you the static files. The cat photo you corrupt today is the same one you will find in your downloads folder tomorrow, with no copy of it living on someone else’s hard drive.
I run my share of toy projects through a NAS for exactly this reason, and Ruin Media slotted in cleanly. Below is the install path I followed on a UGREEN box running UGOS, with the rough edges called out so you do not lose an hour to them.
A container that exists to make your files worse
The pitch is almost defiant in how un-useful it sounds. STANDARD mode gently degrades a JPEG with the kind of compression artifacts you used to see on a 2010 blog. RUINATION mode goes much further, layering in noise, color shifts, and a general “faxed from a submarine” energy. The audio path does the equivalent to a voice memo, in case you ever wanted to send a podcast intro that sounds like a ham radio transmission.
The point is not productivity. The point is that you get a small, opinionated creative toy that respects the boundary between your files and everyone else.
Where it fits:
- You want a photo to feel old, beat up, or anonymous before posting it.
- You want to disguise a voice clip for a group chat or a parody edit.
- You want a self-contained browser tool for a small recurring gag with no SaaS subscription.
- You want something lightweight enough to leave running on a NAS without guilt.
Where it does not fit:
- You need batch processing. This is a drag-and-drop, one file at a time experience.
- You want fine control over the damage. There are two modes, not a slider.
- You care about output formats beyond what your browser can already save.
- You expected it to be a serious image tool. It is a toy that knows it is a toy.
Before you touch the stack file
Two prerequisites will save you from a frustrating afternoon. The first is a working Portainer (a browser dashboard for managing Docker containers) deployment on the NAS, with the Live connect step done. Without that, the Stacks menu in Portainer will not behave the way the rest of this guide assumes. The second is a folder mapped to a path you will remember, because you will need to know where the container is reading static assets from if you ever want to swap in a custom theme or a different default destruction profile.
Things I confirmed before pressing deploy:
- UGOS firmware was current and Docker engine was on a version Portainer 2.x likes.
- The NAS had a DHCP reservation (a fixed local address your router always hands out) so I could bookmark the URL and have it actually resolve a week later.
- I had at least 1 GB of headroom in RAM, even though the container itself only needs a slice of that.
- I had decided on a port that did not collide with anything else in my stack, because I do not enjoy playing port roulette.
None of that is strictly required. It is the checklist I wish I had before I started.
The Portainer stack, end to end
Open Portainer, sign in, and on the left rail click Home, then make sure your endpoint says Live connect. If it does not, fix that first. The Stacks menu behaves differently when the connection is on a non-live mode, and you will chase your tail trying to figure out why your compose file silently fails to apply.
Head to Stacks, click Add stack, give it a name like ruin-media, and paste the standard compose block the upstream README ships. The image tag, the port mapping, and one volume mount for any future customization are all you need. Click Deploy the stack, wait for the success banner, and then open the new container’s published port in your browser. The landing page should render the drop zone with no further setup. There is no database, no account, no API key.
If you want to make sure the container survives reboots, flip the restart policy in Portainer to Unless stopped. That is the one Portainer toggle I always forget on the first try.
What “in the browser” actually buys you
Because the image and audio processing lives in JavaScript, the Docker container is barely doing any work. The CPU spike happens on whatever device you opened the browser on, which is the only correct way to do this kind of tool. If the work were server side, you would be uploading your photos to a stranger’s box, and the entire appeal would collapse.
There is a quiet benefit to that architecture that I appreciate more than I expected. I can run the same container on my laptop, my NAS, and a travel mini-PC, and the experience is identical. The “where is this running” question has a clear answer: wherever I opened the tab. That makes it easy to demo without worrying about which device the file is currently sitting on, and easy to recommend to friends without first explaining my entire home network topology.
It also means there is no telemetry, no signup funnel, and no upsell. The container exists, the page exists, and that is the whole product.
Trade-offs
Ruin Media is small, opinionated, and intentionally limited. That is the whole pitch. If you want a powerful image tool, you want something else. If you want a fun, private, browser-based way to wreck a JPEG for a group chat, this is exactly the right size.
- It does not replace a real image editor. It is a creative toy with two presets.
- It only does one file at a time. There is no batch mode, no queue, no API.
- The Docker container is essentially a static file server. Most of the work happens in your browser tab.
- The destruction profiles are not adjustable in the UI. You would need to fork the repo to add more.
- It is small enough that you might forget it is running. That is fine. The container uses almost no resources.
What I would tell past me
I spent about twenty minutes on this and probably should have spent ten. The only real decision is the port number and whether you want to expose the container on your LAN only or through a reverse proxy. If you already have a working Portainer setup, you are closer than you think. If you do not, set up Portainer first, then come back to this. Everything else is just clicking through a wizard.
The thing I would not skip is reading the README’s compose block carefully once before pasting. The image tag moves, the default port sometimes shifts, and pasting a stale snippet is the kind of low-grade friction that turns a fifteen-minute install into a two-hour debugging session. Read it once, then paste.