More info can be found here.
This is a self-hosted Rickroll container. Point someone at it - a link, a QR code, whatever your heart desires - and they get properly rickrolled: full video and audio of Rick Astley. The video starts playing the instant the page loads; sound kicks in the moment they click anything at all, no matter what it is.
Image is based on nginxinc/nginx-unprivileged, runs as a non-root user, and everything needed to serve the video is baked into the image at build time - no external dependencies at runtime.
docker run -d -p 8080:8080 --name rickroll modem7/docker-rickrollThen visit http://localhost:8080 - see the Configuration example below for a docker-compose version.
Also published to GHCR if you'd rather pull from there: ghcr.io/modem7/docker-rickroll.
- Every browser autoplays a muted video with zero restrictions, but every browser also actively refuses to let a page play sound without a genuine click/tap/keypress first - there's no trick or workaround for this, it's a deliberately and increasingly strictly enforced policy (the same reason YouTube and every other site with audio needs a click too). So the video autoplays muted immediately, and a decoy page state - a stuck-loading spinner or a fake "Something went wrong" site error, picked at random - entices that first click, which is all it takes to unmute. A fake cookie-consent banner sits on top of either one, since that's realistic regardless of what the rest of the page is doing.
- The video keeps loading/playing muted in the background the whole time so it's instantly ready, but it's completely covered by the decoy until the reveal - nothing looks suspicious, and nothing gives it away early.
- Only genuine clicks/taps/keypresses count for this - deliberately not mouse movement or scrolling, since browsers don't count those as real interaction either, and unmuting off one of those just gets the video paused by the browser's autoplay enforcement instead of actually unmuted.
- The video defaults to
object-fit: cover(fills the whole screen), but on a narrow/portrait viewport - basically any phone - that crops a 16:9 video down to a heavily zoomed-in sliver. Below a 1:1 aspect ratio it automatically switches tocontain(letterboxed, but the whole frame is visible) instead, regardless of theOBJECT_FITsetting, since cover never looks right there. Desktop/landscape is untouched. - The video is served through nginx's mp4 module, so seeking/scrubbing and byte-range requests work properly and responses are cached.
- The video isn't stored in git. It's fetched from a GitHub Release asset at build time and baked into the image, so the shipped container is still fully self-contained and works offline - git just doesn't carry the binary around.
- Built for both linux/amd64 and linux/arm64/v8.
| Tag | Description |
|---|---|
| 480p | Video starts automatically. 854x480. |
| 720p | Video starts automatically. 1280x720. |
| latest / 1080p | Video starts automatically. 1920x1080 - latest and 1080p are the same image. |
| 2160p | Video starts automatically. 3840x2160. |
All tags are built from the same image - only the baked-in video resolution differs.
| Variable | Description | Default |
|---|---|---|
| PORT | Changes the port nginx is listening on. | 8080 |
| OVERLAY | Which page state(s) can entice the first click - a comma-separated list from error, loading, one is picked at random per visit. Set to a single value to always use just that one. A cookie-consent banner always shows on top regardless. |
both |
| TITLE | Browser tab title shown once the video is revealed (after the first click/keypress/etc). | Rickroll |
| PRE_TITLE | Browser tab title shown before the video is revealed. | Loading... |
| HEADLINE | Optional heading rendered over the revealed video (e.g. a caption). Leave unset to omit it. | (none) |
| HEIGHT | CSS height of the video element. | 100vh |
| WIDTH | CSS width of the video element. | 100% |
| OBJECT_FIT | CSS object-fit value for the video (cover, contain, etc). |
cover |
| LOOP | Whether the video loops (true/false). |
true |
| VIDEO_FILE | Filename of the video to serve, relative to the web root. | video.mp4 |
services:
rickroll:
image: modem7/docker-rickroll
container_name: Rickroll
ports:
- 8080:8080The video is fetched pre-transcoded from a video asset attached to a GitHub Release at build time, rather than being stored in git. This only matters if you're building the image yourself - the published latest tag already has it baked in.
| Build Arg | Description | Default |
|---|---|---|
| VIDEO_URL | URL the build downloads the (already-transcoded) video from. | video-assets-v1/video-1080p.mp4 |
# build the default (1080p, matches the published `latest` tag)
docker build -t rickroll:1080p .
# point it at a different resolution asset instead
docker build --build-arg VIDEO_URL=https://github.com/modem7/docker-rickroll/releases/download/video-assets-v1/video-720p.mp4 -t rickroll:720p .Transcoding (4K master -> 2160p/1080p/720p/480p mp4s) happens separately, via a manually-triggered GitHub Actions workflow that runs against the master video and uploads the results back to the Release. It only needs to run when the master video changes, not on every build.

