to host a personal imgur
It's the last puzzle piece for this lil' blog o' mine: image hosting.
The hosted version of #WriteFreely, write.as, will do image hosting for you with their paid snap.as service. I'm running the open-source self-hosted version, so I'm on my own.
I wanted something lightweight and easy to maintain with roughly the user experience of #imgur:
- No gallery nonsense, this is just for individual storage and retrieval
- Let me paste images directly from my system clipboard, no mucking about with my local file system
- Everything is public but read-only, so everybody can see what I upload but I'm the only one who can upload
contenders
Chevereto was so slick that I got a little lost in their paid offerings and gave up before I found the open-source piece. Lutim has the functionality I wanted, but the vanilla Docker config wants a database and a cache and job queue and that seems like a lot. Traditional self-hosted solutions like NextCloud, and third-party Git-based solutions like Github, don't let me paste from the system clipboard and immediately use the resulting URL anywhere I want.
bin, on the other hand, had some promise. Just look at that demo. There's nothin' there! Just paste and go! It's perfect!
Chevereto | Lutim | NextCloud | Github | bin | |
---|---|---|---|---|---|
Lightweight | ❌ | ❌ | ❌ | ❌ | ✅ |
Easy to maintain (who am I kidding: Dockerized) | ❓ | ✅ | ❌ | ❌ | ✅ |
No gallery nonsense | ❌ | ❌ | ❌ | ✅ | ✅ |
Some sort of authed-only uploads | ✅ | ✅ | ✅ | ✅ | ❌ |
Paste from system clipboard | ✅ | ✅ | ❌ | ❌ | ✅ |
spinning up a bin
The vanilla docker-compose.yml
is wonderfully sparse:
version: '3.3'
services:
pastebin:
image: wantguns/bin
container_name: pastebin
ports:
- 127.0.0.1:6163:6163
environment:
- BIN_PORT=6163 # Defaults to 6162
- BIN_LIMITS={form="16 MiB"}
volumes:
- ./upload:/upload # upload folder will have your pastes
I cloned the whole repo into /var
0 on my general-purpose Hetzner box and tweaked the config so it allows bigger uploads and runs on the default port (since it's going behind a reverse proxy anyway).
ports:
- 127.0.0.1:98987:6162
environment:
- BIN_LIMITS={form="100 MiB"}
serving bin
with nginx
Copy/pasting the same reverse proxy config around for all my Dockerized bits and bobs has been working great, so I did the same thing again and it continued to work great:
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://0.0.0.0:98987;
proxy_redirect off;
}
The fun part was setting up HTTP Basic Authentication for uploads. #Nginx has good documentation on general setup, so I made myself an htpasswd
file:
sudo htpasswd -c /etc/apache2/.paste.htpasswd ellotheth
and used the limit_except
directive to apply Basic Auth to POST
requests:
location / {
limit_except GET OPTIONS {
auth_basic "ello only";
auth_basic_user_file /etc/apache2/.paste.htpasswd;
}
# rest of the reverse proxy config
Everything I upload is public, but when I upload I get an auth prompt:
Crude, primitive, lightweight, and effective. Databases are for weenies.
gluing bin
and writefreely together
Defining an image in Markdown is simple enough, but I configured this WriteFreely instance with a Content-Security-Policy
header that makes a half-hearted attempt at locking down some common attack vectors. To let til.ello.tech
load images from paste.thereisno.cloud
, I had to add a new directive so clients know my bin
is trusted:
img-src https://paste.thereisno.cloud;
I also had to make sure the bin
host does allow content-sniffing, because bin
(usually) has no idea what media type it's serving and relies on the client to figure it out. I usually throw X-Content-Type-Options "nosniff" always;
in my Nginx server configs, but that's not possible this time.
caveats
Upload management is here's a directory full of randomly named files, have fun. Future me probably has a headache waiting when I need to do cleanup and have to figure out which files actually got used and which didn't. Fortunately, storage is cheap! Cleanup is overrated!
I can't upload anything from my phone. Pasting images (or files, generally) from the Android clipboard isn't supported in Firefox or Chrome, as far as I can tell. The solution to that might be some sort of system-level “share” integration, or even a traditional file upload interface, but it hasn't been a deal-breaker so far.
Images don't get crunched. There's usually a lot optimization that can happen on images destined for the web, and it reduces the file size (a lot) without noticeably impacting the display quality. bin
doesn't do that for me, so if I'm clipping bits of my screen and pasting them, the hosted image can be, uh, chonky.
april (really march) update: explicit content-type headers
As it turns out, I can't just trust that clients will properly sniff the content type and do the right thing. Chrome refused to display uploaded images accessed directly at the URL, preferring instead to trigger a file download. Thanks, Chrome.
Not to worry, back to Nginx: ngx_http_map
will save us. Create a map
in your config that ties URL patterns to content types (I stuck this at the top of my virtual host config file, outside the server
block, no idea if that's right but it worked):
map $request_filename $ctype {
~\.mp4$ video/mp4;
~\.png$ image/png;
}
Then in the server
block, I added the Content-Type
header:
# chrome needs an explicit content-type or it'll just download things
add_header Content-Type $ctype;
map
values default to an empty string if your source value doesn't match one of the patterns, so it won't break anything.
acknowledgements
bin
is a project from @wantguns@mstdn.social and @noracodes@tenforward.social. You should follow them, they seem cool.
#devops #webdev #docker #smallWeb #selfHost #pasteBin
0I know, this should be /opt
, but it's where everything else ended up so TOO LATE NOW