Documentation
What LuisForge is, what's actually in the container images, and why the Cmd/server-hosting tab exists.
Overview
LuisForge is a small self-hosted git host: repos, issues, commit history, a file browser you can upload to
straight from the browser (no git push required), a Docker-build Actions tab, and a Cmd tab
that runs arbitrary commands — including long-running servers — inside disposable containers.
What's in the prebuilt container
The one image offered in the Cmd tab's dropdown (luisforge-alpine-full) is built on top of
alpine:3.19 with the following already installed, so you don't need to apk add
anything yourself before running a command:
| Tool | Why it's there |
|---|---|
| python3 + pip | Run scripts, quick http.server demos, install packages on the fly |
| nodejs + npm | Run JS servers/scripts, npm install a project's deps |
| bash | Alpine ships only sh (busybox ash) by default — bash is added for scripts that need it |
| git, curl, wget | Pull extra dependencies or files mid-command if a script needs them |
| build-base, make, cmake | Compile C/C++ code, native Node/Python extensions that need a real toolchain |
| vim, less | Actually edit or page through a file from inside a running Cmd session |
| htop, tree | See what's actually running / what a directory looks like without piecing it together from ls |
| jq | Parse/filter JSON output from a script or API call without writing a one-off parser |
| ripgrep (rg) | Fast recursive code search — much faster than busybox grep on a real codebase |
| openssh-client | ssh/scp out to somewhere else from inside a run |
| sqlite | Inspect/query a .db file directly, e.g. one a script just produced |
| zip, unzip, tar, gzip | Real archive tools beyond busybox's minimal versions |
| nyancat | No practical reason — it's just there because it's fun, nearly free (a few KB), and it's packaged basically everywhere across Linux distros (Alpine, Debian, Ubuntu, Arch all ship it), so there was no reason not to |
Note: python2 was attempted but dropped — Alpine removed it from its repos after version 3.12, and its OpenSSL1.1/libffi7 dependencies directly conflict with python3's OpenSSL3/libffi8 on a modern base. Forcing both to coexist only works by purging python3 entirely, which defeats the point.
Why use the Cmd tab / bore tunnel
A few different reasons this is useful, depending on what you're doing:
- Instant public demo of anything in a repo. Upload or commit code, hit Run with the port
it listens on, and you get a real public URL (
bore.pub:PORT) in seconds — no cloud account, no hosting provider, no manually configuring port forwarding on your router. - Share work-in-progress without exposing your real IP. bore handles the NAT traversal —
whoever you send the link to hits
bore.pub, not your home network directly. - Zero-setup sandboxed execution. Every run is a fresh container capped at 512MB RAM / 1 CPU, so you can try something you're not fully sure about without it touching your actual machine.
- Testing small services quickly. A Flask/Express API, a static site, a webhook receiver, a toy game server — anything that just needs to exist somewhere reachable for a few minutes.
- Preview-deploy style workflow, self-hosted. Since every run checks out the repo's current HEAD commit fresh, "Run" effectively means "deploy what's on this repo right now" — similar in spirit to Vercel/Netlify preview deployments, minus needing an actual third-party host.
- No lingering cost or cleanup burden. Hit Stop and the container and its bore tunnel are both torn down immediately — nothing keeps running in the background unless you explicitly leave it.
Actions vs Cmd — what's the difference
They look similar but solve different problems:
- Actions = "does this repo's
Dockerfileactually build?" One-shot, runsdocker build ., reports success/failure + full build log. Closer to a CI check. - Cmd = "run this specific command against a prebuilt base image." Can be one-shot (build/test something, see the output) or long-running (host a server, get a public URL via bore).
How the bore tunnel actually works
bore is a lightweight TCP tunnel written in Rust. It solves the same basic problem as ngrok or Cloudflare Tunnel: your LuisForge server (and the containers it spins up) sits behind NAT/a firewall with no public IP, so nothing on the internet can connect to it directly — bore gets around that without you touching your router.
- When you hit Run with "host it publicly" checked, LuisForge starts your command in a container and
asks Docker to publish its port to a random port on
127.0.0.1on the host. - LuisForge then starts a second container —
luisforge-bore— runningbore local <that host port> --to bore.pub. It runs with--network hostspecifically so it can reach127.0.0.1:<port>on the real host, not just its own container's loopback. - The bore client opens a single outbound TCP connection to
bore.pub(a public relay server run by the bore project) and asks for a port to be assigned. Because it's outbound, no firewall/NAT configuration is needed on your end — this is the same trick SSH reverse tunnels and ngrok use. bore.pubreplies with a public port (e.g.bore.pub:41822) and from then on relays any traffic that arrives on that public port back down the existing outbound connection to your bore client, which forwards it to127.0.0.1:<host port>, which Docker forwards into your container.- LuisForge polls that bore container's logs for the line
listening at bore.pub:PORT, parses the port out, and shows it to you as the public address. - Hitting Stop does
docker stopon your app container anddocker rm -fon the bore container — the outbound connection drops,bore.pubfrees the port, and the address stops resolving immediately.
Traffic is not end-to-end encrypted by bore itself — it's a plain TCP relay. Don't send sensitive credentials over a bore-tunneled HTTP endpoint unless the thing you're running adds its own TLS.
Why not Cloudflared?
LuisForge's own backend (and other things on this host, like LuiSearch) actually do use Cloudflare Tunnel elsewhere — so it's not that cloudflared is bad, it's the wrong tool for this specific per-run, ephemeral use case:
- Startup overhead. A cloudflared "quick tunnel" has to negotiate a QUIC connection,
register with Cloudflare's edge, and get assigned a
trycloudflare.comsubdomain — that's several seconds of setup per tunnel, every single time you hit Run. bore's handshake is a single small TCP connection tobore.puband a port assignment — noticeably faster to go from "click Run" to "here's your URL." - No real subdomain stability advantage. The obvious win of cloudflared would be a
nicer permanent hostname — but quick tunnels get a fresh random
*.trycloudflare.comname every single run too, same as bore handing out a fresh random port. Getting an actually stable hostname with cloudflared requires a named tunnel tied to a real Cloudflare account/zone, which is a whole extra setup step LuisForge doesn't want to force on every repo owner. - Bigger binary, more moving parts. cloudflared bundles a full QUIC/HTTP2 stack, WARP
routing, metrics server, and (as seen when we ran it directly on this host) tries to set up an ICMP
proxy and warns/fails without extra host permissions (
ping_group_rangetweaks). bore is a single-purpose static binary that does one thing — open a TCP tunnel — with none of that surface area. - Purpose-built for exactly this. bore's entire reason to exist is "expose one local TCP port through a NAT, ephemerally, with zero config." Cloudflared is a general-purpose reverse-proxy/access product with a lot of features (Zero Trust policies, WARP, Argo Smart Routing) that are irrelevant overhead for a disposable per-container tunnel that might live for two minutes.
- Smaller, simpler sidecar image.
luisforge-boreis Alpine + a single ~3MB static binary. A cloudflared-based equivalent image would be considerably larger and slower to build/run as a throwaway per-request sidecar container.
In short: cloudflared is the right call for something long-lived and account-backed (like LuiSearch's own backend tunnel); bore is the right call for "give me a URL for the next two minutes and then disappear."
Why not Windows or macOS?
The prebuilt image is Alpine Linux, and that's not really a style choice — the alternatives are either technically impossible or a completely different, much heavier setup on this specific host:
- macOS containers effectively don't exist. Apple's license terms restrict macOS to
running on Apple hardware, so there's no official "macOS base image" for Docker at all, containerized
or otherwise — anyone offering "macOS in a container" is either lying or running a real Mac behind the
scenes (which is exactly what this session did earlier for iOS builds — a real GitHub Actions
macos-14runner, not a local container). Not something LuisForge's Linux host can do locally, full stop. - Windows containers need a Windows host. Docker's Windows container mode isn't cross-platform the way Linux containers are — it requires the Docker daemon itself to be running on Windows (with Hyper-V or the Windows container runtime). This host runs Linux, so the daemon can only ever run Linux containers; there's no "run a Windows base image" option available here at all, only Linux distros (or Linux VMs pretending to be something else, which isn't a real Windows environment).
- Even where it's possible, it's drastically heavier. A Windows Server Core base image alone is multiple gigabytes, versus Alpine's ~7MB base — completely at odds with the "spin up instantly, tear down instantly" model this feature is built around.
- Alpine matches the host for free. Since this host is already Linux, Linux containers run natively with no virtualization layer in between — that's what makes 512MB/1-CPU containers boot in a second or two instead of needing a VM to boot first.
In short: this isn't "Alpine because it's trendy," it's "Alpine because it's the only option that's actually a real, lightweight, instantly-startable container on this host" — Windows and macOS aren't just worse choices here, they're not really on the table.
Why not Ubuntu (or Debian, etc.)?
Unlike Windows/macOS, this one's a genuine trade-off, not an impossibility — Ubuntu/Debian-based images work perfectly fine in Docker on this host. Alpine was picked for a few concrete reasons, with an honest downside too:
- Base image size. Alpine's base is ~7MB; Ubuntu's is ~29MB, and once you add the equivalent python3/nodejs/npm/git/bash toolchain on top the gap widens further (Ubuntu's package manager pulls in more transitive dependencies by default). For containers meant to boot and die within a couple minutes, that difference in pull/build/boot time adds up.
- Smaller attack surface. Fewer packages installed by default means fewer CVEs to track in a base you didn't choose to audit — relevant here since (per the transparency section above) this image isn't independently verified beyond "started from the official base."
- apk is fast. Alpine's package manager resolves and installs quickly, which matters
when
ensure_prebuilts_pulled()is meant to make the image "immediately available" rather than something you wait on.
The honest downside: Alpine uses musl libc instead of glibc, which is smaller but not 100% ABI-compatible with software built and only tested against glibc — this is exactly what caused the python2 saga documented above (Alpine dropped python2 after 3.12, and pulling in an old build meant for a different libc/OpenSSL/libffi generation turned into an unwinnable dependency conflict). Ubuntu/Debian's glibc base would have sidestepped that specific problem, at the cost of a noticeably larger, slower-to-boot image for every single run. For this feature's "instant, disposable, mostly scripting/serving" use case, that trade favored Alpine — but if you need to run something that's picky about glibc specifically, Alpine is the wrong base and Ubuntu would genuinely be the better call.
Transparency — image provenance
The Docker images used here are not published, audited, or open source
artifacts. luisforge-alpine-full and luisforge-bore are built locally
by this specific LuisForge instance from a Dockerfile that lives alongside the server code —
they are not pulled from Docker Hub, not signed, and not independently verifiable by you as a user beyond
trusting whoever runs this instance.
luisforge-alpine-fullstarts from the officialalpine:3.19base image (that part is a real, public, verifiable image) and layers on packages installed viaapk add— python3, nodejs/npm, bash, git, curl, wget, nyancat.luisforge-borealso starts fromalpine:3.19, but theborebinary itself is copied in from this host's local install rather than fetched from bore's official release artifacts at build time — so you're trusting this host's copy of the binary, not a checksummed upstream release.- The upstream bore project itself is open source (MIT-licensed, source on GitHub) — the caveat above is specifically about how the container image bundling it was built here, not about bore's own licensing.
Limits & safety
- Every container:
--memory 512m --cpus 1hard cap. - Only the repo owner can trigger Actions runs, Cmd runs, or host a server on that repo.
- Containers get normal outbound network access by default (can
curl,npm install,git clone, etc.) — nothing is sandboxed offline. - Hosted servers are not persistent — if the LuisForge backend process restarts, any currently running Cmd/server containers and their bore tunnels are not automatically resumed.