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