How to use vrok
Free and open source. vrok is not a file-hosting site. It is a local server plus a tunnel: one command publishes a file, a folder, or a running HTTP app through a URL that is meant to die.
What it is
Your laptop sits behind NAT. Nobody on the internet can open a
connection to it. vrok starts a small HTTP server on
loopback, opens an outbound tunnel, and prints a public URL.
Visitors hit that URL; bytes are read from your disk — or proxied
to localhost — while they wait.
That covers three different jobs with the same command:
- A file — video with seeking, a PDF, an installer, anything.
- A folder — browsable listing, or a built site / Playwright report served as the real thing.
-
A running server —
vrok localhost:3000reverse-proxies it, WebSockets included, so hot reload still works for the other person.
Nothing is uploaded. Closing the terminal, hitting the TTL, or revoking the share is what stops the URL. If the recipient needs the file after you shut the lid, this is the wrong tool — see the comparison.
Updating
vrok never checks for updates in the background. When you want the newest release, ask for it:
vrok update
It finds the newest release on GitHub, downloads the build for
your platform, checks it against the published checksum, makes
sure it runs, and only then replaces itself. If Homebrew, Scoop or
go install put vrok on your machine, it prints that
tool's command instead, so the package manager stays in charge.
Add --check to only see whether there is a newer
release.
Newest release: see releases · what changed
Install a specific version
If a new release breaks something for you, go back to the one that worked. Pick the version from the changelog:
vrok update --version
v0.6.0
Plain vrok update returns you to the newest release
later. Releases older than 0.6.0 do not have
vrok update, so install those with the install
script, which takes the version from VROK_VERSION:
- macOS · Linux
-
curl -fsSL https://raw.githubusercontent.com/AliJabbar034/vrok/main/install.sh | VROK_VERSION=v0.5.2 sh - Windows
-
$env:VROK_VERSION='v0.5.2'; irm https://raw.githubusercontent.com/AliJabbar034/vrok/main/install.ps1 | iex
The same variable pins a version in CI, so every run uses the release you tested with. Homebrew and Scoop only install the newest release. From source:
go install github.com/AliJabbar034/vrok/cmd/vrok@v0.6.0
Without vrok update
Older versions answer unknown command "update".
Update once the way you installed, below. After that,
vrok update works.
1. Stop running shares
Press Ctrl+C in any terminal running vrok.
On Windows this is required: a running vrok.exe
cannot be replaced. On macOS and Linux an open share keeps running
the old version until you start it again.
2. Run the update for your install method
- Install script macOS · Linux
-
curl -fsSL https://raw.githubusercontent.com/AliJabbar034/vrok/main/install.sh | sh - PowerShell script Windows
-
irm https://raw.githubusercontent.com/AliJabbar034/vrok/main/install.ps1 | iex - Homebrew macOS · Linux
-
brew update && brew upgrade --cask AliJabbar034/tap/vrok - Scoop Windows
-
scoop update; scoop update vrok - Go any platform
-
go install github.com/AliJabbar034/vrok/cmd/vrok@latest
Not sure how you installed it?
Ask your shell where vrok lives:
which vrok on macOS and Linux,
(Get-Command vrok).Source in PowerShell. Then match
the path:
| Path contains | Installed with |
|---|---|
/opt/homebrew/, linuxbrew |
Homebrew |
/usr/local/bin/, ~/.local/bin/
|
Install script. On an Intel Mac, Homebrew also uses
/usr/local/bin: if
ls -l $(which vrok) points into
Caskroom, it is Homebrew.
|
\AppData\Local\Programs\vrok\ |
PowerShell script |
\scoop\shims\ |
Scoop |
/go/bin/ |
Go |
3. Check it
vrok --version
It prints vrok version X.Y.Z (…), where
X.Y.Z is the newest release above without the
v. A build from go install prints
dev instead; that is expected, and it is still the
latest code.
Still showing the old version?
-
Two copies on your PATH.
vrok doctorlists them. The first one found wins. List them withwhich -a vrokorGet-Command vrok -All, then delete the old one or update it with its own method. -
Your shell remembers the old path. Run
hash -r, or open a new terminal. On Windows always open a new terminal after installing. -
Homebrew says it is up to date. Its tap list is
stale:
brew updatefirst, then upgrade again. -
“Access is denied” on Windows. vrok is still
running somewhere. Stop it, or check Task Manager for
vrok.exe. -
Something else?
Open an issue
with the output of
vrok --versionand the path from above.
Commands
vrok ./cut-03.mp4 # one file
vrok ./playwright-report # a whole directory
vrok report.pdf shot.png a.mp4 # several files, one index
vrok localhost:3000 # a local HTTP server
vrok share ./x # same as vrok ./x
A file
Recognised types get a preview (player, image, highlighted
source). Everything else gets a download card with the right
Content-Type. There is no allow-list: a 4 GB disk image, an
.xlsx, or a file with no extension all work.
A folder
Visitors get a listing. If the folder contains
index.html, that file is served at the root — which
is why vrok ./dist looks like the real site. Add
?list=1 to any directory URL to force the listing.
Every listing has a Download all (.zip) button,
and any subfolder can be zipped on its own. The zip is built while
it is sent, so nothing is written to disk first. One zip counts as
one download against --downloads.
A localhost app
vrok does not replace your framework. It reverse-proxies whatever is already listening. Range requests and WebSocket upgrades pass through on the default Cloudflare route, so Vite / Next hot reload reaches the person you sent the link to.
WebSocket upgrades are not carried through
--tunnel relay yet. If you use your own relay for
live frontend work, visitors must refresh.
Flags
| Flag | What it does |
|---|---|
-i |
Three questions first: who can open it, how long it lasts, and the download limit. Then prints the equivalent flags. |
--ttl 30m |
How long the share lives. 45s,
30m, 2h, 1d,
1w. Default: until you stop it.
|
--downloads 5 |
Close after five downloads. Seeking inside a video does not spend one. |
--password |
Prompt for a visitor password. Also reads
VROK_PASSWORD. Stored as Argon2id, never
written to disk.
|
--qr |
Print a QR code next to the URL. |
--local |
LAN only. No public tunnel. The banner says
your local network.
|
--tunnel local |
This machine only. A 127.0.0.1 URL cannot be
sent to anyone.
|
--tunnel <name> |
auto (default), cloudflare, or
relay.
|
--name client-report |
Display name on the share and in vrok list.
|
--port 8080 |
Pick the local listen port instead of a free one. |
-v |
Request and tunnel diagnostics. |
While a share is running,
c copies the URL, q prints a QR code,
p adds a password, e changes the expiry,
1 makes it one-time, and x stops. Each
change keeps the same URL. Scripts and CI never see the hotkey
bar.
Large files
While someone downloads, one line under the banner shows how it is going. When they finish, a summary stays behind:
↓ 1.2 GB / 4.0 GB · 30% · 38 MB/s · 1m left
↓ Sent 4.0 GB in 1m 48s · 38 MB/s average
-
Resumable. A dropped download picks up where it
stopped, and resuming does not spend one of your
--downloads. - Your computer stays awake. While a transfer is running, vrok keeps the machine from going to sleep on its own, and lets it sleep again 30 seconds after the last one ends. Closing the lid still sleeps.
- Whole folders in one go. See A folder above.
-
A fingerprint to check against. The file page
shows the file’s SHA-256, worked out in the background after the
share starts. The recipient compares it with
shasum -a 256 fileon macOS or Linux, orcertutil -hashfile file SHA256on Windows.
Speed is set by your upload bandwidth and the tunnel, not by vrok: every byte comes from your machine. Watch a transfer on the homepage.
Managing shares
vrok list # every share running on this machine
vrok list --json # the same, for scripts
vrok revoke a82kd9 # kill one URL now
vrok stop --all # stop every vrok process
vrok config set ttl 30m # store a default
vrok update # install the newest release
vrok doctor # check this machine for common problems
There is no database. Each sharing process reports itself over a socket in your state directory. When the process exits, it disappears from the list.
When something breaks
Run the doctor first. It checks the things that most often stop vrok from working, changes nothing, and finishes in a few seconds:
vrok doctor
✓ Install /usr/local/bin/vrok (the install script)
! PATH 2 copies of vrok; the first one runs:
/usr/local/bin/vrok
/Users/ana/go/bin/vrok
Delete the ones you do not use.
✓ Config defaults (no config file)
✓ cloudflared fetched by vrok
! Port 8080 in use; `vrok --local` needs it free, or pass --port
✓ Cloudflare reachable (64 ms)
✓ Version newest release
-
✗ Cloudflare unreachable. Your network blocks
the tunnel. Share on the local network with
--local, or use a relay you control. - ✗ Config. The config file does not parse. Fix it, or delete it to go back to the defaults.
- ! PATH or Version. See Still showing the old version?
Still stuck? Open a bug report and paste the doctor's output into it.
What visitors see
The link opens a vrok page in the same enamel locker theme as this site — mark, countdown, one Download button. They can view what the browser can show and download the original. A mock of that page is on the homepage. The table below is presentation only. Any type can be shared.
| Type | Preview |
|---|---|
| JPG, PNG, WebP, GIF, SVG | Image |
| MP4, WebM, MOV, MKV | Video player with seeking |
| MP3, WAV, FLAC, OGG | Audio player |
| The browser’s PDF viewer | |
| Markdown, JSON, source, logs | Rendered or highlighted text |
| HTML / a folder with index.html | The page itself |
| ZIP, JAR | Listing, without extracting |
| XLSX, DOCX, PPTX, DMG, EXE, APK | Download card, correct Content-Type |
| Anything else | Download card |
Range requests work end to end, including through a tunnel, so video seeking and resume work. Edits you make on disk show up when the visitor refreshes; vrok does not cache a copy.
Who can open the URL
The HTTP server always binds 127.0.0.1. Reaching it
from anywhere else is a tunnel’s job. The banner states the reach
so you do not paste a loopback URL into a chat.
| Command | Who can open it |
|---|---|
vrok ./x |
Anyone with the link (auto tunnel) |
vrok ./x --local |
Your network |
vrok ./x --tunnel local |
This machine only |
auto prefers a relay you configured, then an
installed client, then fetches cloudflared once into
the cache. That download is announced in the terminal. Store a
private default with vrok config set tunnel local.
Security and privacy
The full model is in docs/security.md. The short version:
-
Only what you shared is reachable.
vrok ./demo.pdfserves that file. The folder it sits in, sibling files, and anything above it are not on the URL. A visitor cannot ask for another path. -
The visitor never names a file. The URL carries
a 128-bit token from
crypto/rand. There is no?path=/Users/you/secret. -
Path confinement.
../, encoded paths, and symlinks that escape the shared directory all 404 the same way a missing file does. A file replaced by a symlink after you shared it is refused too. - Uniform failure. Expired, revoked, exhausted and unknown shares look identical. Probing reveals nothing.
- Passwords are Argon2id digests in memory, never plaintext on disk. The unlock cookie is HttpOnly.
- No analytics. Download counts stay in your terminal. Nothing is sent to a vrok server, because there isn’t one on the default path.
-
Default reach is public via Cloudflare’s quick
tunnel. The banner says so. Use
--localor--tunnel localwhen it should not leave the building. A public URL still expires. - If you share a folder that contains a secret, the secret is shared. vrok confines access to the directory you chose. It does not judge what is in it.
A vulnerability in those guarantees belongs in private reporting, not a public issue.
For teams evaluating vrok
vrok is a program you run, not a service you sign up to. There is no account, no vrok server and no stored copy of anything you share, so there is no vendor holding your data and nothing to certify. What matters is where the bytes travel:
- Default route. Through a Cloudflare quick tunnel. Cloudflare terminates TLS, so it can see the traffic in transit, and its terms apply.
-
--localkeeps a share on your network;--tunnel localkeeps it on your machine. - Your own relay keeps the whole path on infrastructure you run.
Releases carry SHA-256 checksums, a software bill of materials per
archive, and signed build provenance you can check with
gh attestation verify. Dependencies are scanned for
known vulnerabilities on every change and weekly. The full threat
model is in
docs/security.md.
Responsible use
You are responsible for what you share and who you send the link to. Anyone with the URL can open it until it expires or you stop it.
- On the default route your share passes through Cloudflare's free quick tunnels, which are meant for testing and come with Cloudflare's terms. Do not use them to host phishing, malware or anything you have no right to share.
-
For confidential, personal or regulated data, use
--local, your own relay, or a password and a short--ttl.
Privacy
The CLI collects nothing. It sends no telemetry
and reports no usage. Download counts are kept in the running
process and printed in your terminal when the share ends. It
connects only to the tunnel or relay you use, to GitHub once to
fetch cloudflared if it is missing, and to GitHub and
Cloudflare when you run vrok update or
vrok doctor. It never checks for updates on its own.
This site sets no cookies. It counts visits with Umami, which keeps no cookies, builds no profile and follows no one to other sites. It records the page, referrer, country and browser, plus a few actions: copying an install command, clicking a download or GitHub link, playing a demo scene, how the star request was answered, whether a guide section helped, and script errors so broken pages get fixed. If your browser sends Do Not Track, nothing is counted.
Fonts are served from the site itself. Your browser asks GitHub's public API for the latest release and the changelog, and remembers in local storage that you dismissed the release banner or the star request, and which sections you already rated.
Share pages set only the cookies they need: one after a correct password, and one that lets a resumed download continue without counting again. Both are signed, HttpOnly session cookies, and stop working the moment the share ends.