I recently wanted to share a 30MB file with someone, which is still a harder problem than it should be1. Too big for most email providers, and I didn't want to have to run through the list of cloud file store options to see which we had in common. In the past, I've handled this by using Tailscale Funnel to temporarily expose a Caddy file_server↗ for long enough for the other party to retrieve the file(s). Hardly an elegant solution.
This time, though, I stumbled upon Gokapi↗ which checks a lot of my boxes2:
- Open source
- Self-hosted with a single container image
- End-to-end encryption
- Auto-expiring and download-limited links
- OIDC authentication for being able to upload files
- S3-like object storage support
I particularly like that I can connect Gokapi to external storage (to avoid using up all the storage on my VPS), that links (and corresponding files) will be automatically removed after a defined time period or number of downloads, and that it doesn't allow internet randos to upload files (my compute resources are finite, after all).
It only took me a few minutes to deploy the Gokapi container on an existing Hetzner VPS, link it to Pocket ID for auth, connect the storage backend to Bunny's (beta) S3 storage offering↗, and get on with sharing files.
Deployment
The Gokapi repo has an example docker compose file↗ to make it easy to get started. I just created an /opt/docker/gokapi/ folder on my server to hold the app's configuration and data (the integrated sqlite database) and then brought up the single-container stack:
services:
gokapi:
image: f0rc3/gokapi:latest
container_name: gokapi
ports:
- "127.0.0.1:53842:53842"
volumes:
- /opt/docker/gokapi/data:/app/data
- /opt/docker/gokapi/config:/app/config
restart: always
environment:
TZ: UTC
The repo also provides an example .env file↗ in case you want to use environment variables to configure the deployment. This time I skipped right past that and instead used the web interface to perform the configuration. More on that in a moment.
But first, I used Caddy↗ to proxy the container with automatic TLS magic:
gokapi.example.com {
reverse_proxy http://localhost:53842
log {
output file /var/log/caddy/gokapi.log
}
}
After doing a quick docker compose up -d to start Gokapi and sudo caddy reload -c /etc/caddy/Caddyfile to ask Caddy to please start serving the new thing I can point my browser to https://gokapi.example.com/setup and get on with the configuratin'.
Hurry up!
Until the initial configuration is complete, anyone can access that URL to configure the instance. So you'll probably want to speed through the first-run config or configure Caddy to only allow access from your home IP for now↗.
After the first config, the /setup endpoint will only be available if you explicitly ask for it at container startup, and it will then also be protected by a randomly-generated username and password.
I just stick with the default sqlite database and initial webserver options, but I update two values on the Webserver 2/2 screen:
- Public facing URL is set to
https://gokapi.example.com/to reflect the base URL where this thing will be served publicly. - Redirection URL points to my little jwq.lol↗ landing page. Basically there's no reason for an outside user to poke around the main page of my Gokapi instance so anyone that hits the index page will get sent elsewhere instead.
I want to use my Pocket ID setup for logging in to Gokapi's admin page, so I enable OIDC auth:
I then created a new OIDC client in my Pocket ID deployment:
Notable configuration details from that:
- Client Launch URL:
https://gokapi.example.com - Callback URLs:
https://gokapi.example.com/oauth-callback - PKCE: disabled
Then I could use the Client ID and Client Secret for configuring the auth back in Gokapi:
The key here is that the Admin email address must match the email for your user in Pocket ID since that's how you'll be granted access.
Then I moved on to the storage setup. I wanted to use external S3-compatible object storage so I wouldn't have to worry about blowing up the disk on my VPS. So I selected Cloud Storage from the appropriate dropdown:
I'm using Bunny's new S3-compatible storage offering but the setup should be pretty similar for any provider:
CORS
Clicking the Test configuration button yielded a warning:
Test OK. WARNING: CORS settings do not allow encrypted downloads.
I know that Bunny's S3 support is still in early stages and doesn't yet support custom CORS rules and I suspect this warning may be related to that. Encrypted downloads work fine for me, but if you get a similar warning and they don't work, try enabling proxy downloads at the bottom of the previous storage configuration page.
And I want to use full end-to-end encryption; that makes the client work a little harder since it handles the encryption/decryption when files are uploaded/downloaded, but I like that the (possibly-sensitive) files will be completely encrypted at every step along the way.
Firefox issues
There's a pinned issue↗ on the Gokapi repo about Firefox (and derivatives) having trouble downloading files which were uploaded with end-to-end encryption. I haven't run into issues yet, probably since I've so far just been sharing files with sizes in the tens of megabytes. But if you intend to share much larger files you may want to use one of the lower encryption levels until that issue gets fixed.
All that's left is to click the big friendly Submit button to save my settings and get on with sharing files!
Usage
Now that setup is complete, if I just point my web browser to https://gokapi.example.com... I get immediately redirected to my profile page↗. Oh yeah, I did set up that redirect. Well it's cool that it works!
To be able to do stuff in Gokapi, I can instead head to https://gokapi.example.com/login, and this time I get redirected to the Pocket ID auth challenge:
Once I'm in.3, I immediately get hit with a screen telling me about the randomly-generated password to support end-to-end encryption.
This gets generated in my browser (not on the server) and and will be used for encrypting files uploaded the server. The server never knows about this password and thus wouldn't be able to access the encrypted even if it wanted to.
Save this somewhere safe!
The prompt isn't kidding: this random password won't be displayed again. It never gets uploaded to the server and exists solely in your local browser session. You'll need to re-enter the same password any time you log into Gokapi from a new machine (or erase your browser's local storage).
If you lose it, you can rerun the Gokapi setup to generate a new one - but you won't be able to access any of the files which were already uploaded.
Anyhoo, once I've confirmed receipt of the encryption password I can get on in to the main Gokapi interface:
I've got options there for limiting how many times a file can be downloaded, automatically removing it after a set number of days, and optionally setting a password for the file. These options will be applied to the next uploaded file.
I went ahead and left those options on their default settings, and uploaded a 1GB file to test things out.
Now that the file has been uploaded, I can click the little "copy URL" button at the right to grab the URL I can send to my friend. I could also click the little pencil icon to change the download limit, expiry, or password options for the file.
I'd like to see what the experience looks like on the other side of things, so I'm gonna go ahead and paste the download link into another browser and download the file:
After downloading the file once (the limit configured for that file) it gets automatically removed from the server and subsequent visits to the URL are met with a 404.
Reconfiguration
Once the initial setup has been completed, the only way to revisit and change settings is to restart the container and pass the --reconfigure flag. I usually do this by stopping the compose stack and then firing off a standalone container:
$ docker run --rm -p 127.0.0.1:53842:53842 \
-v /opt/docker/gokapi/data:/app/data -v /opt/docker/gokapi/config:/app/config \
f0rc3/gokapi:latest /app/run.sh --reconfigure
██████ ██████ ██ ██ █████ ██████ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ███ ██ ██ █████ ███████ ██████ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██ ██ ██ ██ ██ ██
Gokapi v2.2.4 starting
###################################################################
Use the following credentials for modifying the configuration:
Username: q12RsR
Password: 9yyrmvLPNN
###################################################################
Please open http://127.0.0.1:53842/setup to setup Gokapi.
I can then log in to https://gokapi.example/com/setup using the provided credentials and make my changes. Once I'm done, I exit the running container and restart the compose stack to pick up the changes.
Conclusion
I'm pretty pleased with this setup so far, and it seems to work rather well for my needs. I really like that the files automatically get removed after downloading so I don't have to worry about cluttering up storage somewhere, and being able to use external object storage means I don't have to provision a ton of storage on the server itself.












