# 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 key–only 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//` 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//` 6. Writes your SSH key to: - `/mnt/microcontainers//.ssh/authorized_keys` 7. Fixes ownership to match the in-container user: - `chown -R 1000:1000 /mnt/microcontainers/` 8. Picks a free port in a range (e.g. `20000–21000`). 9. Starts a Docker container: ```bash docker run -d \ --name \ --memory=512m \ --memory-swap=512m \ -p HOST_PORT:22 \ -v /mnt/microcontainers/:/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. `20000–21000`) * 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 VM’s 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 you’ll 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. `30000–30100`: ```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`. It’s intended to run as root (or equivalent) on a box you control. * **SSH ports exposed on the host** Example: `20000–21000`. 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/` * Optionally `rm -rf /srv/microcontainers//` 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