One home for all your locally-developed apps. Add each app once, then launch it, watch its logs, and see its UI — all from a single web page. No more digging through terminals or Docker Desktop to bring an app up.
- A unified gallery — every app you add shows up as a card with its status (running / stopped).
- Start apps with a click — the gallery runs your app's start command for you (Docker, Node, Python, or anything else).
- Groups: start a whole stack at once — put an app's backend, frontend and any other services in one group, then press Start all to bring them all up together.
- See each app's UI inside the gallery — the app is shown embedded in a preview pane. To make this work even for apps that normally refuse to be embedded, the gallery quietly mirrors each running app and removes the browser rule that blocks embedding.
- Stored settings per app — the start command, stop command, folder, and port are saved, so next time it's just one button.
- Node.js (v18 or newer). Check with
node --version. - Whatever each app itself needs (Docker, Python, etc.) must already work from your terminal.
git clone <your-fork-url> LocalGallery
cd LocalGallery
npm install
npm startThen open http://localhost:4747.
To use a different port: PORT=5000 npm start. The gallery only answers on this machine — see Security & limits if you need to change that.
Click + Add app. The form has two tabs:
- Single app — one app at a time (the classic form).
- Multiple apps — several apps in one go, all landing in the same group. Handy when an app is really a backend plus a frontend plus a worker. Pick or create the group at the top, give a shared folder once, then fill in a block per app. Any block that leaves Folder blank uses the shared folder.
Either way, each app has these fields:
| Field | What it means | Example |
|---|---|---|
| Name | Shown on the card | My Web App |
| Group | (optional) which group the app belongs to | My Full Stack |
| Type | Just picks an icon and suggests commands | Docker |
| Port | The port the app's own server listens on | 3000 |
| Folder | The app's folder on disk (absolute path) | /Users/you/projects/my-app |
| Start command | What brings the app up | docker compose up -d |
| Stop command | (optional) what shuts it down | docker compose down |
| Open path | Path to load in the preview | / |
Picking a Type pre-fills the usual commands, which you can edit.
A group is a named box of apps that belong together — for example an API and the web page that talks to it. Each group is a band across the gallery with its own colour, icon and buttons; apps that aren't in any group sit in an Ungrouped band at the bottom.
What you can do:
- Make a group — click + New group in the top bar, or choose "+ Create a new group…" on the Multiple apps tab.
- Put an app in a group — hover a card and click ↦, or set the Group field while editing the app. Doing this never changes anything else about the app.
- Start the whole group — Start all in the group's header starts every app in it at the same time. Apps that are already running are left alone, and the header says how many were started, how many were already up, and how many failed.
- Stop the whole group — Stop all stops every app in the group, using each app's own stop rule.
- Start one app on its own — every card still has its own Start / Stop buttons, so a group never forces you to run everything.
- Fold a group away — click the ▾ arrow (or the empty part of the header). The gallery remembers which groups you folded shut in this browser.
- Delete a group — this only removes the box. Its apps stay in the gallery and move to Ungrouped.
Apps in a group start all at once, not one after another. If your frontend needs the backend to be answering before it starts, start the backend first with its own Start button, then use Start all (or just press Start all again — apps already up are skipped).
- Docker apps — start
docker compose up -d, stopdocker compose down. Because Docker runs the app in the background, set both commands. - Node apps — start
npm run dev(ornpm start). Leave Stop command blank; the gallery will stop the process it started for you. - Python apps — start
streamlit run app.py,python app.py, oruvicorn main:app --port 8000. Leave Stop command blank.
The Port must match the port the app actually serves on — that's what the gallery checks to know if the app is "running", and what it previews. If you run several apps that all default to the same port, give each one a unique port so they don't collide.
- A small Node + Express server (in
server/) does three jobs: saves your app settings and groups todata/apps.json, runs start/stop commands and captures their logs, and mirrors each running app on its own local port with the embedding block removed. - Groups are just a list in the same file. Each group has an id, a name, a colour and an icon; each app stores the id of the group it belongs to (or nothing at all). Starting a group is simply "start each of its apps", so nothing about a single app changes when you put it in one. An app list saved before groups existed still opens fine — every app in it counts as ungrouped.
- The browser page (in
public/) is plain HTML/CSS/JS — no build step. - Started apps run in their own process group, so stopping one cleanly shuts down its child processes too.
- Each app's output is written to its own log file in
data/logs/(not sent through a pipe to the gallery), so an app keeps working even if the gallery itself goes away, and the log panel can still show earlier output after a gallery restart. - Stop always works, even for an app the gallery didn't start itself (say the gallery crashed while the app kept running): the gallery finds the leftover process by the port it's using and stops it. Closing the gallery's terminal window also shuts down the apps it started, so leftover processes shouldn't appear in the first place.
See data/apps.example.json for the shape of a saved app and group. Your real config lives in data/apps.json, which is created on first run and kept out of git.
- This machine only, by default. The gallery answers on
127.0.0.1— your own computer — and nowhere else. That matters because it runs your saved start commands: anyone who can reach it can run programs on your machine. It also refuses requests addressed to any other name, and requests caused by any other website, so a page you happen to visit can't quietly start or stop your apps. - Opening it up is deliberate, and rarely a good idea.
HOST=0.0.0.0 npm startmakes it answer on every network connection, andALLOWED_HOSTS=my-mac.local,192.168.1.20lists the extra names it will accept. There is no password on the gallery, so only do this on a network you fully trust. It prints a warning when you do. - It runs whatever you configure. A start command is just a shell command — only add apps and commands you trust, the same way you'd trust anything you type into your own terminal.
- The embedding mirror removes only what blocks framing — the whole
X-Frame-Optionsheader (its only job is to block framing) and theframe-ancestorsrule from an app's content-security-policy. The app's other rules, such as which scripts may run, are passed through untouched. This affects only the local mirror, never the original app. - Logs show the output of the start/stop command you gave. For a Docker app started with
-d, container logs live in Docker; the panel shows theup/downoutput. - Deleting an app from the gallery only removes it from the list — it never touches the app's files.
- If a preview shows "App not reachable", the app either isn't started yet or is using a different port than the one you entered.
LocalGallery/
├─ server/
│ ├─ index.js # web server + API
│ ├─ store.js # reads/writes data/apps.json
│ ├─ processManager.js # start/stop apps, capture logs, report status
│ ├─ proxyManager.js # per-app mirror that allows embedding
│ └─ util.js # port helpers
├─ public/ # the gallery UI (index.html, styles.css, app.js)
├─ docs/
│ └─ images/ # screenshots used by this README
├─ data/
│ ├─ apps.example.json # sample of the saved-apps format
│ └─ apps.json # your saved apps (created on first run, git-ignored)
└─ package.json
Issues and pull requests are welcome. To run it locally while developing, use npm run dev — it restarts the server automatically when you change a file in server/.
MIT © Johnny Chen

0 comments
log in to comment.