| micro-alpine-dev | ||
| micro-debian-dev | ||
| micro-fedora43-dev | ||
| app.py | ||
| fastfetch_config.jsonc | ||
| README.md | ||
| start-server.sh | ||
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(withsudo) - SSH key–only authentication
- Unique SSH port on the host
- Dedicated 3 GB ext4 filesystem mounted as
/home/micro
- User
- 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:
- Validates your SSH public key.
- Generates a unique container name:
- e.g.
mc-debian-myproject-8c5268
- e.g.
- Creates a tenant directory on the host:
/srv/microcontainers/<container_name>/
- Creates a sparse 3 GB
disk.img:fallocate -l 3G disk.imgmkfs.ext4 disk.img
- Mounts it on the host:
/mnt/microcontainers/<container_name>/
- Writes your SSH key to:
/mnt/microcontainers/<container_name>/.ssh/authorized_keys
- Fixes ownership to match the in-container user:
chown -R 1000:1000 /mnt/microcontainers/<container_name>
- Picks a free port in a range (e.g.
20000–21000). - Starts a Docker container:
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
-
Shows you an SSH command like:
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)
sudoenabled inside the container
- User:
-
Port range allocation
- Host ports auto-assigned from a configured range (e.g.
20000–21000) - One container per SSH port
- Host ports auto-assigned from a configured range (e.g.
-
Image-agnostic
- Comes with sample Debian & Fedora dev images
- Easy to add your own
micro-*-devimages
Repository layout
Example layout:
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):
#!/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:
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:
cd docker/micro-debian-dev
./build-dockerfile.sh
Fedora 43 dev box (micro-fedora43-dev)
docker/micro-fedora43-dev/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:
cd docker/micro-fedora43-dev
./build-dockerfile.sh
The Flask app (app.py)
Core ideas in app.py:
-
Configuration:
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 writeauthorized_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):
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
requirements.txt:
Flask>=3.0,<4.0
2. Run the Flask server
python app.py
By default it listens on 0.0.0.0:5000.
You may also want a firewall rule to allow your LAN:
# 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:
- Open
http://HOST:5000/(replaceHOSTwith your VM’s IP/hostname). - Paste your SSH public key (one line, e.g.
ssh-ed25519 AAAA... user@host). - Choose an image (
debianorfedora). - Optionally add a label (e.g.
project-x-dev). - Click Provision MicroContainer.
If successful you’ll see something like:
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:
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:
HOSTNAME = "your.vps.domain" -
To change ports to e.g.
30000–30100: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.1and reverse tunnel - Or put it behind a VPN / reverse proxy with auth
- Bind to
-
Runs as root The app calls
fallocate,mkfs.ext4,mount,chown, anddocker 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 rmumount /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:
app.secret_key = os.environ.get("MICROCONTAINERS_SECRET_KEY", "dev-only-not-secret")
For anything beyond local testing, set a real random secret:
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