mentalnet-microcontainers/README.md
2025-11-23 19:02:38 -05:00

435 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# mentalnet-microcontainers
A DIY provisioning cloud for your homelab.
`mentalnet-microcontainers` gives you a tiny, self-hosted control plane for spinning up Docker containers and using them like lightweight VMs:
- Web UI to paste an SSH public key and pick a base image
- Automatically provisions a container with:
- User `micro` (with `sudo`)
- SSH keyonly authentication
- Unique SSH port on the host
- Dedicated 3 GB ext4 filesystem mounted as `/home/micro`
- Designed for “I just want a throwaway dev box on my own hardware”, not full multi-tenant hosting
---
## What this actually does
When you hit the web UI and create a “microcontainer”, the app:
1. Validates your SSH public key.
2. Generates a unique container name:
- e.g. `mc-debian-myproject-8c5268`
3. Creates a tenant directory on the host:
- `/srv/microcontainers/<container_name>/`
4. Creates a sparse 3 GB `disk.img`:
- `fallocate -l 3G disk.img`
- `mkfs.ext4 disk.img`
5. Mounts it on the host:
- `/mnt/microcontainers/<container_name>/`
6. Writes your SSH key to:
- `/mnt/microcontainers/<container_name>/.ssh/authorized_keys`
7. Fixes ownership to match the in-container user:
- `chown -R 1000:1000 /mnt/microcontainers/<container_name>`
8. Picks a free port in a range (e.g. `2000021000`).
9. Starts a Docker container:
```bash
docker run -d \
--name <container_name> \
--memory=512m \
--memory-swap=512m \
-p HOST_PORT:22 \
-v /mnt/microcontainers/<container_name>:/home/micro \
micro-debian-dev:latest # or micro-fedora43-dev:latest
````
10. Shows you an SSH command like:
```bash
ssh micro@your-hostname -p 20013
```
From your perspective, it feels like “click → get a tiny VM with a 3 GB home and sudo.”
---
## Features
* **DIY provisioning cloud**
Turn one Docker host into a small pool of micro-VM-like environments.
* **Web UI (Flask)**
* Paste SSH key
* Choose base image (Debian / Fedora)
* Optional label to tag the container name
* **Per-tenant filesystem**
* Each microcontainer gets its own 3 GB ext4 filesystem
* Mounted as `/home/micro`
* Prevents a single tenant from filling the host via `$HOME`
* **SSH-only login**
* User: `micro`
* Auth: SSH public key only (no passwords)
* `sudo` enabled inside the container
* **Port range allocation**
* Host ports auto-assigned from a configured range (e.g. `2000021000`)
* One container per SSH port
* **Image-agnostic**
* Comes with sample Debian & Fedora dev images
* Easy to add your own `micro-*-dev` images
---
## Repository layout
Example layout:
```text
mentalnet-microcontainers/
app.py
requirements.txt
docker/
micro-debian-dev/
Dockerfile
build-dockerfile.sh
micro-fedora43-dev/
Dockerfile
build-dockerfile.sh
README.md
```
You can add more images under `docker/` later (e.g. `micro-alpine-dev`, `micro-arch-dev`, etc.).
---
## Base images
### Build script convention
Each image directory contains a simple build script:
`docker/micro-debian-dev/build-dockerfile.sh` (same pattern for Fedora):
```bash
#!/usr/bin/env bash
set -euo pipefail
IMAGE_NAME="$(basename "$(pwd)")"
echo "Building Docker image: ${IMAGE_NAME}:latest"
docker build -t "${IMAGE_NAME}:latest" .
```
Name your directory the same as the image you want (`micro-debian-dev`, `micro-fedora43-dev`, etc.) and the script will build `IMAGE_NAME:latest` automatically.
---
### Debian dev box (`micro-debian-dev`)
`docker/micro-debian-dev/Dockerfile`:
```dockerfile
FROM debian:stable-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
apt-get install -y --no-install-recommends \
openssh-server \
sudo \
ca-certificates \
git \
curl wget \
vim nano \
htop \
build-essential && \
rm -rf /var/lib/apt/lists/*
# Create 'micro' user with fixed uid/gid 1000
RUN useradd -m -u 1000 -U -s /bin/bash micro && \
echo "micro:ChangeMe123" | chpasswd && \
usermod -aG sudo micro
# Prepare .ssh directory (runtime volume will mount over /home/micro)
RUN mkdir -p /home/micro/.ssh && \
chown -R micro:micro /home/micro && \
chmod 700 /home/micro/.ssh
# SSH server config: key-only login, use ~/.ssh/authorized_keys
RUN sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config || true && \
sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config || true && \
sed -i 's/^#KbdInteractiveAuthentication yes/KbdInteractiveAuthentication no/' /etc/ssh/sshd_config || true && \
sed -i 's/^#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config || true && \
sed -i 's|^#AuthorizedKeysFile.*|AuthorizedKeysFile .ssh/authorized_keys|' /etc/ssh/sshd_config || true && \
echo 'UsePAM no' >> /etc/ssh/sshd_config
RUN mkdir -p /var/run/sshd && \
ssh-keygen -A
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
```
Build:
```bash
cd docker/micro-debian-dev
./build-dockerfile.sh
```
---
### Fedora 43 dev box (`micro-fedora43-dev`)
`docker/micro-fedora43-dev/Dockerfile`:
```dockerfile
FROM fedora:43
RUN dnf -y update && \
dnf -y install \
openssh-server \
sudo \
ca-certificates \
git \
curl wget \
vim nano \
htop \
gcc gcc-c++ make && \
dnf clean all && \
rm -rf /var/cache/dnf
RUN useradd -m -u 1000 -U -s /bin/bash micro && \
echo "micro:ChangeMe123" | chpasswd && \
usermod -aG wheel micro
RUN mkdir -p /home/micro/.ssh && \
chown -R micro:micro /home/micro && \
chmod 700 /home/micro/.ssh
RUN sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config || true && \
sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config || true && \
sed -i 's/^#KbdInteractiveAuthentication yes/KbdInteractiveAuthentication no/' /etc/ssh/sshd_config || true && \
sed -i 's/^#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config || true && \
sed -i 's|^#AuthorizedKeysFile.*|AuthorizedKeysFile .ssh/authorized_keys|' /etc/ssh/sshd_config || true && \
echo 'UsePAM no' >> /etc/ssh/sshd_config
RUN mkdir -p /var/run/sshd && \
ssh-keygen -A
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
```
Build:
```bash
cd docker/micro-fedora43-dev
./build-dockerfile.sh
```
---
## The Flask app (`app.py`)
Core ideas in `app.py`:
* Configuration:
```python
IMAGES = {
"debian": {
"docker_image": "micro-debian-dev:latest",
"label": "Debian Dev Box (micro-debian-dev)"
},
"fedora": {
"docker_image": "micro-fedora43-dev:latest",
"label": "Fedora 43 Dev Box (micro-fedora43-dev)"
},
}
DEFAULT_IMAGE_KEY = "debian"
DATA_ROOT = "/srv/microcontainers" # per-tenant metadata + disk.img
MOUNT_ROOT = "/mnt/microcontainers" # where disk.img files are mounted
HOSTNAME = "arthur.lan" # only used for display in UI
PORT_RANGE_START = 20000
PORT_RANGE_END = 21000
```
* Each POST to `/`:
* Validates the SSH key
* Picks an image
* Generates a container name
* Calls `ensure_tenant_disk()` to create/mount the 3 GB ext4 filesystem and write `authorized_keys`
* Chooses a free port in the range
* Calls `run_docker_container()` with:
* `-p HOST_PORT:22`
* `-v home_mount:/home/micro`
Returned info includes container name, host port, and an SSH command.
---
## Running the app
### 1. Install dependencies
Create a venv (optional but recommended):
```bash
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```
`requirements.txt`:
```text
Flask>=3.0,<4.0
```
### 2. Run the Flask server
```bash
python app.py
```
By default it listens on `0.0.0.0:5000`.
You may also want a firewall rule to allow your LAN:
```bash
# Example with iptables (adjust to your LAN subnet)
iptables -A INPUT -s 192.168.86.0/24 -p tcp --dport 5000 -m state --state NEW -j ACCEPT
```
### 3. Use the UI
From a machine on your LAN:
1. Open `http://HOST:5000/` (replace `HOST` with your VMs IP/hostname).
2. Paste your SSH public key (one line, e.g. `ssh-ed25519 AAAA... user@host`).
3. Choose an image (`debian` or `fedora`).
4. Optionally add a label (e.g. `project-x-dev`).
5. Click **Provision MicroContainer**.
If successful youll see something like:
```text
Container created!
Image: debian — Debian Dev Box (micro-debian-dev)
Container name: mc-debian-project-x-8c5268
Host port: 20013
SSH command:
ssh micro@arthur.lan -p 20013
```
---
## Configuration
You can tune basic behavior at the top of `app.py`:
```python
DATA_ROOT = "/srv/microcontainers"
MOUNT_ROOT = "/mnt/microcontainers"
HOSTNAME = "arthur.lan" # used only for the SSH hint in the UI
PORT_RANGE_START = 20000
PORT_RANGE_END = 21000
```
Examples:
* On a VPS, you might set:
```python
HOSTNAME = "your.vps.domain"
```
* To change ports to e.g. `3000030100`:
```python
PORT_RANGE_START = 30000
PORT_RANGE_END = 30100
```
---
## Security notes
This is a **homelab prototype**, not hardened multi-tenant hosting.
Things to be aware of:
* **No auth on the UI**
Anyone who can reach port 5000 can create containers.
Recommended:
* Bind to `127.0.0.1` and reverse tunnel
* Or put it behind a VPN / reverse proxy with auth
* **Runs as root**
The app calls `fallocate`, `mkfs.ext4`, `mount`, `chown`, and `docker run`.
Its intended to run as root (or equivalent) on a box you control.
* **SSH ports exposed on the host**
Example: `2000021000`.
Use your firewall / router to decide who can reach them.
* **No automatic cleanup yet**
Stopping/removing containers and unmounting disks is manual for now:
* `docker ps`, `docker stop`, `docker rm`
* `umount /mnt/microcontainers/<container_name>`
* Optionally `rm -rf /srv/microcontainers/<container_name>/`
Use this like a DIY “micro-VPS factory” in your lab, not a fully managed public cloud.
---
### Secret key
Flask uses `app.secret_key` to sign session data and flash messages.
In `app.py` this is read from the `MICROCONTAINERS_SECRET_KEY` environment variable:
```python
app.secret_key = os.environ.get("MICROCONTAINERS_SECRET_KEY", "dev-only-not-secret")
````
For anything beyond local testing, set a real random secret:
```bash
export MICROCONTAINERS_SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_hex(32))')"
```
---
## Ideas / future work
* Add a simple **cleanup UI** for stopping/removing containers and unmounting disks.
* Optional **basic auth** or token-protected UI.
* More **SKUs**:
* `micro-alpine-dev`, `micro-arch-dev`, `micro-nix-dev`, etc.
* Stats page showing:
* Used vs total space per tenant
* Running containers and their ports