# ezshare: a local file server with a browser on the other end

> ezshare starts an HTTP server in whatever directory you point it at, lists everything inside it, zips directories on the fly and lets any device on the same network upload or download, with two-way clipboard sharing and a QR code for convenience. It is MIT licensed, installs as a single npm package, and the interesting engineering is in the Docker image rather than the app.

**mifi/ezshare** — Easily share files, folders and clipboard over LAN - Like Google Drive but without internet

- Repository: https://github.com/mifi/ezshare
- Stars: 802 · Forks: 56
- Language: TypeScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/mifi-ezshare

## A directory listing that becomes a transfer tool

ezshare is described as a simple file server that lets you easily share many big files like photos and videos with friends, or between your devices, over a local network without requiring an internet connection. The README compares it to a self hosted Google Drive without the internet part.

The mechanism is deliberately plain. It starts an HTTP server that lists all files and directories in the directory where it is started from. Anyone who connects can download files, or an automatically generated ZIP of a whole directory, which is the part that makes it practical for large media folders. The client can upload files back to the server through the browser, and the clipboard can be shared in both directions. A QR code is generated for convenience, so the other device does not have to type an IP address.

The feature list emphasises the constraints it was built around. Local two-way transfers without going through the internet. Full quality photos and videos sent to others without needing a fast connection. Really big files and directories, and a lot of files. Auto ZIPs of directories on the fly. Two-way clipboard sharing. Video and image playback with slideshows in the web client.

The client runs anywhere with a modern browser, iOS, Android, Mac, Windows and more, while the server itself must run on a Mac, Windows or Linux computer. That split is what makes it a phone-to-laptop tool rather than a peer to peer system.

The primary language of the project is TypeScript, and the repository is a yarn workspace with packages under packages/, holding the web client, a shared library, the CLI and the Electron shell.

## Two install paths that disagree on the port

There are three ways to install ezshare and they are not interchangeable, which is the single most likely thing to trip you up.

The npm route needs Node.js and is one global install:

```bash
npm install -g @ezshare/cli
```

The standalone route avoids Node.js entirely. If you do not want to install Node.js, you can download the Electron based executable of ezshare from the Releases page.

And the Docker route exists so that the awkward dependencies are already handled. The README explains why: running ezshare with Docker ensures all dependencies like ffmpeg and xvfb are pre-installed and configured correctly. With Compose it is a single command:

the start command is `docker compose up --build`

Then you open http://localhost:3003, and files placed in the ./my-shared-data folder, which is created automatically in the project root, appear in the application under /shared.

With the manual Docker CLI the steps are a build and a run, and the run command has to map the port and mount a volume:

the build command is `docker build -t ezshare .`

the run command is `docker run -p 3003:3003 -v $(pwd)/my-shared-data:/shared ezshare`

The port difference is the trap. The Docker image starts ezshare with --port 3003, so the container listens on 3003, and this differs from the standalone CLI, which listens on port 8080 by default and which you change with --port. The Compose file in the repository maps 3003 to 3003, and the container runs as the node user.

There is a version naming change to be aware of as well. The migration from v1 to v2 is two commands:

```bash
npm uninstall -g ezshare
npm i -g @ezshare/cli
```

So if you had the old global package installed, the new one lives under a different name, and running the old binary name after upgrading the package is not going to do what you expect.

## Running it, and the volume permission fix nobody expects

The usage sequence is four steps, and the fourth is the one people forget. Open a terminal, change into the folder you want to share, run ezshare, open the browser on the other device to the printed URL, and start uploading or downloading. The README notes that the two devices need to be on the same WiFi, or possibly a personal hotspot.

You can also pass the path directly:

```bash
ezshare /your/shared/folder
```

and the help output is available through ezshare --help.

The permission problem is specific to the container. Since the container runs as UID 1000, the node user, the host directory mounted at /shared must be writable by UID 1000. If you hit Permission Denied errors on Linux or Mac, the fix runs on the host:

```bash
mkdir -p ./my-shared-data
chown -R 1000:1000 ./my-shared-data
```

That is the kind of detail that separates a README written by someone who has run the thing from one written by someone who has not. The Compose file mounts ./my-shared/path to /shared, and sets user: node explicitly, with a comment noting that the Dockerfile USER instruction already handles it.

One inconsistency is worth flagging for anyone reading the compose file against the documentation. The Compose file mounts ./my-shared/path, while the README and the manual docker run command both use ./my-shared-data. The directory is created automatically for you by the README instructions, so in practice the Compose path is the one you will have to create yourself.

The Dockerfile explains the rest. It is a two stage build. The builder stage is a full node:20 image with git and corepack enabled, which clones the repository and runs yarn install --immutable followed by yarn build. The runner stage is node:20-slim, described in the comment as hundreds of megabytes smaller, and it installs xvfb, ffmpeg, xsel and dbus, then copies only the built packages across and removes the source folders, since only the dist output is needed. It then installs only production dependencies with yarn workspaces focus --production.

The xvfb and xsel entries are the technical explanation for the two features that would otherwise look magical. xvfb is a virtual framebuffer, which is how a graphical Electron process runs headless inside a container, and xsel is the X selection clipboard tool, which is how the two-way clipboard sharing works in that environment.

## Sharing over the internet, with tunnels

The README has a section for sharing over the internet without NAT, and it does not pretend that the tool does this itself. It points at two services.

For Ngrok:

```bash
ngrok http http://ip.of.ezshare:8080/
```

For a Cloudflare tunnel:

```bash
cloudflared tunnel --url http://ip.of.ezshare:8080/ --no-autoupdate
```

In both cases you then share the URL you are given. Note that the target is port 8080, the standalone CLI port, not the container port, and the URL uses the host address of the machine rather than a hostname, so the host part is something you fill in.

This is the part of the project where the security story changes, and the README does not say anything about authentication, access control or transport security for the shared directory. The application is a directory listing with upload, and if you expose that through a tunnel you are exposing the contents of the folder to whoever has the URL. The link to a curated list of tunneling alternatives is offered for people who need a different mechanism.

The clipboard sharing feature deserves a note here too, because it is the capability that most rewards caution. Sharing a clipboard between your desktop and a phone over a tunnel means whatever you copy, including passwords, travels through that path.

For day to day use, the local network is the intended mode, and the two devices being on the same WiFi is the only requirement. The QR code is generated for convenience so the phone can connect without typing an address.

## Where this approach stops being appropriate

The limits here are structural, and they follow from the design of a directory server.

The first is the absence of access control. The server lists the directory and serves what is in it. There is no account, no password and no per-file permission, so the trust model is entirely the network boundary. On a home WiFi that is usually fine. On a shared office network, a campus network or anything with guest access, it is not, and a tunnel to it makes it worse rather than better.

The second is that it serves whatever directory you point it at, including everything in it. There is no exclude list and no include pattern, so the safe way to use it is to point it at a folder containing only what you intend to share, which is what the usage instructions imply by making you cd into the shared folder first.

The third is the platform split. The server must be a Mac, Windows or Linux computer, and the client needs a modern browser. That is a wide reach, but it means the tool cannot run on an iOS or Android device as the host, and it cannot be a headless service on a small box without one of those operating systems.

The fourth is that this is a convenience tool for moving files, not a sync system. There is no versioning, no conflict resolution and no background synchronisation. If you edit a file on the phone, the copy on the laptop does not update unless you re-share it.

The alternatives differ in what they cost. A dedicated file transfer tool over the same local network gives you a similar experience with a smaller attack surface, usually a single file at a time rather than a browsable tree. A proper file sync service gives you background syncing and versioning, and it wants an account, a server and your trust in the operator. And a plain SMB or NFS share does the same job with the operating system's own permissions, if both devices are on the same network and you are willing to configure it.

The comparison listed in the README points at two other projects, qr-filetransfer and qr-fileshare, both of which take the QR code and transfer a single file or folder rather than serving a browsable tree. That difference in scope is the real choice here: one file at a time with minimal exposure, or a browsable server with more capability and more exposure.

## The v2 rewrite and the release process

The version numbers in this repository are worth understanding, because the manifest and the tags tell different stories.

The package.json at the root reads version 1.12.0, and the release history lists v2.0.1 from 2026-03-13, v2.0.0 from 2025-08-27 and v1.11.5 from 2024-11-15. The npm package names changed in the 2.0 line, with the CLI now published as @ezshare/cli rather than ezshare, which is exactly the migration the README documents.

The release procedure in the README is unusually explicit, and it is a good template for a multi-package project. First push and wait for green GitHub Actions. Then version whichever packages changed, one workspace at a time:

```bash
yarn workspace @ezshare/web version patch
yarn workspace @ezshare/lib version patch
yarn workspace @ezshare/cli version patch
yarn workspace @ezshare/electron version patch
```

Then stage the package.json files and commit with the message Release. Then build and publish to npm, again workspace by workspace:

```bash
yarn build
yarn workspace @ezshare/web npm publish
yarn workspace @ezshare/lib npm publish
yarn workspace @ezshare/cli npm publish
```

After the git push, you trigger a workflow dispatch to build the Electron version, wait for the run, then edit the draft release to add notes and check that the artifacts got attached. The manual release steps are there because the four packages version and publish independently, and the Electron binary needs a CI run to produce.

For development there are separate targets. Running the CLI locally is yarn dev:cli, running Electron locally is yarn dev:electron, and packaging is yarn build followed by yarn workspace @ezshare/electron package. The root package.json defines the workspaces as packages/*, uses yarn 4.12.0 as the package manager, requires Node 18 or newer, and sets the module type to esm. The lint and build scripts use yarn workspaces foreach across all packages.

On maintenance, the repository is not archived and the last push was on 2026-08-02, with the v2.0.1 release in March 2026. The licence is MIT, with the LICENSE file at the repository root, and the author is Mikael Finstad.

## Conclusion

Adopt ezshare if you regularly move photos and videos between your laptop and a phone or tablet on the same WiFi and you want something that runs from a terminal with no account and no cloud. Do not adopt it for sharing over the internet without reading the tunnel options, and skip it if you need access control, because the server is a plain directory listing. Verify first that port 8080 for the standalone CLI or 3003 for the container is free, that the shared directory is writable by the user the process runs as, and that the version you install is the v2 package name @ezshare/cli rather than the old ezshare global.

## FAQ

### How do I install ezshare?

With Node.js installed, run `npm install -g @ezshare/cli`. If you would rather not install Node.js, you can download the Electron based executable from the Releases page, and there is also a Docker route that pre-installs the ffmpeg and xvfb dependencies.

### What port does ezshare use?

The standalone CLI listens on port 8080 by default, and you change it with --port. The Docker image starts ezshare with --port 3003, so the container listens on 3003 and the Compose file maps 3003 to 3003.

### How do I share files with ezshare?

Open a terminal, change into the folder you want to share, run ezshare, and open the printed URL in a browser on the other device. You can also pass the path directly with `ezshare /your/shared/folder`, and both devices need to be on the same WiFi or a personal hotspot.

### What causes Permission Denied when running ezshare in Docker?

The container runs as UID 1000, the node user, so the host directory mounted at /shared must be writable by that UID. On Linux or Mac, create the directory and change its ownership with `mkdir -p ./my-shared-data` and `chown -R 1000:1000 ./my-shared-data`.

### How do I share ezshare over the internet?

Through a tunnel, using either `ngrok http http://ip.of.ezshare:8080/` or `cloudflared tunnel --url http://ip.of.ezshare:8080/ --no-autoupdate`, and then sharing the URL you are given. The README does not document any authentication for the shared directory.

### What is the difference between the v1 and v2 packages?

The 2.0 line renamed the npm package, so migrating means `npm uninstall -g ezshare` followed by `npm i -g @ezshare/cli`. The repository is a yarn workspace of four packages, the web client, a shared library, the CLI and the Electron shell, which are versioned and published independently.

## Sources

- [Issues](https://github.com/mifi/ezshare/issues)
- [License: MIT](https://github.com/mifi/ezshare/blob/master/LICENSE)
- [mifi/ezshare on GitHub](https://github.com/mifi/ezshare)
- [README](https://github.com/mifi/ezshare/blob/master/README.md)
- [Releases](https://github.com/mifi/ezshare/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mifi-ezshare
