Early release: expect some rough edges. Feedback welcome.
Get your LiteBin running and deploy your first app in under 5 minutes.
Install Master
Orchestrator + Dashboard + Caddy
Create Admin Account
Set up dashboard access
Add Worker Nodes (Optional)
Scale to multiple servers
SSH into your Linux VPS and run:
curl -fsSL https://l8b.in | bash
Open PowerShell as Administrator and run:
iex (irm https://l8b.in/windows.ps1)
Fresh VPS? Optionally harden it before installing — SSH keys, firewall, fail2ban: basic security setup script →
The installer will prompt for:
example.com)l8bin)master_proxy)After the installer finishes, open your dashboard URL and create an admin account. Even if you plan to use only the CLI, don't skip this — the dashboard is publicly accessible until the first admin is created, and l8b login uses these credentials.
https://<dashboard-subdomain>.<your-domain>
Built from what you chose during install: the dashboard subdomain (default l8bin) + your domain.
DNS: Make sure your domain's A record points to your server IP before running the installer.
To scale to multiple servers, you can add worker nodes running the LiteBin Agent.
Tip: Prefer not to do this by hand? The dashboard — Nodes → Add Node — guides you through the same flow.
1. Generate mTLS certificates on the master server:
curl -fsSL https://l8b.in | bash -s certs
2. On each Linux worker server, run the installer in agent mode:
curl -fsSL https://l8b.in | bash -s agent
The installer will prompt for:
worker-1)certs command aboveThe LiteBin Agent (worker daemon) currently requires a Linux environment. You can still use your Windows Master to manage Linux workers, but Windows-native worker support is Coming Soon.
If you have a Linux VPS ready, you can switch the OS toggle at the top of this guide to see the worker setup instructions.
How do you want to deploy your apps?
Build on your machine, stream the image to your server. No registry needed.
Install CLI locally:
Option A: Prebuilt binary (easiest)
curl -fsSL https://l8b.in | bash -s cli
Option B: Build from source (requires Cargo)
git clone https://github.com/mtsandeep/l8bin.git
cd l8bin
./setup/build.sh
bash install.sh cli
Option A: Prebuilt binary (easiest)
iex "& { $(irm https://l8b.in/windows.ps1) } -Cli"
Option B: Build from source (requires Cargo)
git clone https://github.com/mtsandeep/l8bin.git
cd l8bin
.\setup\build.ps1
powershell -File .\install-windows.ps1 cli
The installer automatically configures your PATH. Open a new terminal and run l8b --version to verify.
Add ~/.local/bin to your shell profile manually:
# bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
# zsh (macOS default)
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc && source ~/.zshrc
Refresh your current PowerShell session (no restart):
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
Alternatively, manually add to your User PATH:
[Environment]::SetEnvironmentVariable('Path', [Environment]::GetEnvironmentVariable('Path', 'User') + ';' + "$env:USERPROFILE\.local\bin", 'User')
Login once — the session is stored:
l8b login --server https://l8bin.example.com
Note: If running LiteBin locally, use http://localhost:5080 as the server. Cloud-hosted instances work normally over HTTPS.
Interactive deploy (auto-detects Dockerfile or uses Railpack):
l8b ship
Or deploy non-interactively:
l8b deploy --project myapp
Port is auto-detected from EXPOSE. Use --port to override.
The easiest way to manage your apps — start, stop, configure, and view logs with a few clicks.
Go to your LiteBin dashboard
https://l8bin.example.com
Click "Deploy"
Enter an image from any public registry
Configure and deploy
Set project name, port, env vars
Example images:
nginx:alpine
node:20
ghcr.io/org/app:latest
Public registries only for now — private registry support is coming soon. Need something custom? l8b ship (CLI tab) builds on your machine and streams the image directly.
Dashboard Management:
App will be live at https://<app-name>.your-domain.com
Push to your repo and the site updates itself. Let LiteBin wire it up, or write the workflow yourself.
Recommended: Connect GitHub — zero YAML, zero secrets to paste
Create your GitHub App
Dashboard → Settings → GitHub → Create GitHub App. It's your own private app — you own it, and you choose which repos it can see.
Pick a repo and a project
LiteBin commits the workflow (.github/workflows/litebin-<project>-deploy.yml) and sets the secrets (L8B_SERVER + L8B_TOKEN_<PROJECT>) for you.
Choose the trigger
Auto-deploy on every push to your branch (builds run on GitHub-hosted runners), or keep it manual — trigger deploys from the GitHub Actions tab or the LiteBin dashboard.
Manual: write the workflow yourself
Exactly what Connect GitHub commits for you — same workflow, same secrets, done by hand. Create .github/workflows/deploy.yml:
name: LiteBin Deploy
on:
push:
branches: [main]
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mtsandeep/l8bin-action@v0.2.0
with:
server: ${{ secrets.L8B_SERVER }}
token: ${{ secrets.L8B_TOKEN_MYAPP }}
project_id: myapp
Required GitHub Secrets:
L8B_TOKEN_<PROJECT>). Generate from Dashboard → Settings → API TokensEvery push to main will now auto-deploy to your LiteBin cluster.
Deploy multi-service apps with docker-compose.yml. LiteBin auto-detects it when you run l8b ship or upload it from the dashboard.
services:
api:
image: node:24-alpine
ports:
- "3000" # LiteBin routes HTTP to this port
environment:
- DATABASE_URL=${DATABASE_URL:-postgres://postgres:password@db:5432/myapp}
working_dir: /app
volumes:
- app-data:/app/data # Named volume (scoped to litebin_<project>_app-data)
- ./logs:/app/logs # Bind mount (under projects/<project>/logs/)
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
deploy:
resources:
limits:
memory: 256M
cpus: "0.5"
labels:
litebin.public: "true" # Mark as the public service (optional)
db:
image: postgres:16-alpine
ports:
- "5432" # Only accessible within the Docker network
environment:
- POSTGRES_DB=myapp
- POSTGRES_PASSWORD=${DB_PASSWORD:-password}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
app-data:
pgdata:
image, build (context + dockerfile)command, entrypointworking_dir, userenvironment (with ${VAR} interpolation)ports (TCP + UDP, with protocol)depends_on (long form with conditions)volumes (named volumes + bind mounts)healthcheckrestart (no, always, unless-stopped)stdin_open, ttydeploy.resources.limits (memory + cpus)shm_size, tmpfs, read_onlycap_add, cap_drop, extra_hostslabels (litebin.public to pick public service)projects/<project>/.env for runtime secrets. LiteBin auto-detects changes and recreates the container on next wake. See env-secrets.md.litebin_<project>_ prefix. Bind mounts with relative paths are stored under projects/<project>/.LiteBin picks one service as the public HTTP endpoint (routed through Caddy with TLS). Detection order:
litebin.public: "true" label80 or 443If multiple services have ports and none match the above, l8b ship asks you to pick the public service. For CI (l8b deploy), the first service is auto-selected.
If a Dockerfile exists in your project root, l8b ship uses it to build your image. Otherwise, Railpack auto-detects your framework.
FROM node:24-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:24-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
l8b ship checks for Dockerfile in your project root. If found, it builds with Docker. Otherwise, it uses Railpack.FROM ... AS builder pattern as shown above.l8b ship --dockerfile Dockerfile.dev to specify a different filename.l8b ship --secret .env to pass secrets at build time (baked into the image).--platform flag needed.l8b ship auto-detects the port from EXPOSE in your Dockerfile. If multiple ports are exposed, the first one is used. If none, you'll be prompted (default: 3000).