Wimage - Hosting Open-Source Image Uploader with Podman and external S3 Storage
I was looking for a simple image hosting platform that I could self-host. Picsur and some other candidates are no longer maintained or are simply overkill. My Frankenstein’s monster of a solution for fetching images from my notes app is not the way to go.
So, after talking about this with a friend of mine, he simply created a simple platform for this - open-source and self-hostable. This is not an ad and I am not getting paid (although I should be @Frank).
Wimage is a simple open-source image uploading and hosting platform designed for personal and private use with guest keys for your friends, family and team. It has a great API and a simple UI in the browser. Metadata is stored in a local SQLite database and the images are saved in S3-compatible storage. By default, it uses a local Garage instance (which we don’t need) and Docker.
In this article I want to share a short instruction on how to host Wimage yourself with Podman and an external S3-compatible storage provider.
Requirements #
Just my personal setup, feel free to change things up. I assume the following is set up already:
- Ubuntu 24.04
- Installed: Podman (should work with Docker)
- S3-compatible hosting provider (I use Backblaze), as well as network access to it (443/TCP)
- Network access to Codeberg via HTTPS as we have to build the image ourselves
- Reverse proxy for TLS termination - I use nginx, but Caddy and Apache will will work as well
- Domain pointing to the host server
Installation #
So, this is a fairly new project and this setup it working as of now and can change any time. I’ll update this article if something changes.
Some considerations, I use the -e env variable options in the Podman run command instead of the .env file and a bind mount instead of a name volume for the local database for the meta data.
All configuration options can be found in the README on Codeberg.
Building Image #
mkdir -p /path/to/your/wimage
cd /path/to/your/wimage
git clone https://codeberg.org/fracwaffle/wimage.git
cd wimage
podman build -t localhost/wimage:latest .
# That is it
Preparation for S3 #
Fill out the information of the S3-ednpoint and make sure that the container can access it via HTTPS:
-e S3_ENDPOINT_URL="https://s3.eu-central-003.backblazeb2.com" \
-e S3_BUCKET="name-of-your-bucket" \
-e S3_ACCESS_KEY_ID="344325252535235235" \
-e S3_SECRET_ACCESS_KEY="HgfjhkfhFHhthdhdf/gsrtgg" \
-e S3_REGION=eu-"central-003" \
Side note: make sure that the bucket is private, use the NAME of the bucket, and not the ID and generate an API key that can write+read. The process is different with every provider, but should be straight forward.
Admin Access to application #
Generate a long and secure string - this key is being used for uploads, guest key generation and general administration.
Use openssl rand -base64 32 your your favorite password manager to generate it.
-e ADMIN_KEY=longandsecurekeylgrhgihrsigdsrighr \
-e BASE_URL="https://wimage.example.com" \
Storage for SQLite #
As mentioned before, I use a bind mount - feel free to use a named volume:
Create directory:
mkdir /path/to/your/wimage/data
And those options for later.
-e DATABASE_PATH=/app/data/wimage.db \ #just keep it
-v /path/to/your/wimage/data:/app/data
Optional Hardening #
Making the container inside read-only, strips all capabilities that are not needed and prevents the container from getting more. Will talk about more those option in a future post.
--read-only \
--security-opt no-new-privileges \
--cap-drop ALL \
--cap-add=CHOWN \
--cap-add=NET_BIND_SERVICE \
--cap-add=SETGID \
--cap-add=SETUID \
Putting everything together #
So, the application in the image listens to port 8000/TCP and we are going to limit it to our localhost so only the traffic through the reverse-proxy can reach it.
The combined command looks like this then:
podman run --replace -d \
--name container-wimage \
-p 127.0.0.1:8000:8000 \
-e S3_ENDPOINT_URL="https://s3.eu-central-003.backblazeb2.com" \
-e S3_BUCKET="name-of-your-bucket" \
-e S3_ACCESS_KEY_ID="344325252535235235" \
-e S3_SECRET_ACCESS_KEY="HgfjhkfhFHhthdhdf/gsrtgg" \
-e S3_REGION=eu-"central-003" \
-e ADMIN_KEY=longandsecurekeylgrhgihrsigdsrighr \
-e BASE_URL="https://wimage.example.com" \
-e DATABASE_PATH=/app/data/wimage.db \ #just keep it
-v /path/to/your/wimage/data:/app/data
--read-only \
--security-opt no-new-privileges \
--cap-drop ALL \
--cap-add=CHOWN \
--cap-add=NET_BIND_SERVICE \
--cap-add=SETGID \
--cap-add=SETUID \
localhost/wimage:latest
The container should be running!
A quick health check can be done with curl:
curl -L 127.0.0.1:8000/health
{"status":"ok","database":"ok","s3":"ok"}
Now, point your reverse proxy with the certificate and domain to 127.0.0.1:8000 and the service should be accessible.
The web interface can be accessed via https://wimage.example.com and the usage of the API is explained in the README and API Swagger description via https://wimage.example.com/docs
Have fun
Upgrade to new Version #
Check for new version on the repo.
cd /path/to/your/wimage/wimage
git pull main
podman build -t localhost/wimage:latest .
# Check change notes and adjust env variables IF needed, otherwise just run the same command again --replace will replace the old container