Creating Your First Isolated Linux Container with SSH Access: A Step-by-Step Guide

Introduction
Have you ever wondered how tools like Docker create isolated environments? In this guide, we’ll build a simple container from scratch using Linux namespaces. You’ll create a fully isolated environment with its own network and filesystem, then access it remotely via SSH or NSENTER. – No prior container experience needed!
What You’ll Learn
- How Linux namespaces work
- Setting up a minimal container filesystem
- Configuring isolated networking
- Create a Custom cgroup
- Limit CPU Usage
- Startup a webserver & host a simple webpage
- nsenter or SSH access with Dropbear (a lightweight SSH server) optinal
Prerequisites
- A Linux system (Ubuntu 22.04 used here)
-
sudo
access - Basic terminal familiarity
- Internet connection
Step 1: The Complete Script
Here’s the full script we’ll be using. Don’t worry if it looks complex—we’ll break it down line by line!
Save this as create-container.sh
:
#!/usr/bin/env bash
# setup_isolated_container_ssh.sh
# This script sets up a fully isolated container environment with its own network namespace using a veth pair,
# then chroots into a minimal filesystem and starts Dropbear SSH daemon and a BusyBox HTTP server.
set -eo pipefail
# CONFIGURATION VARIABLES
CONTAINER_ROOT="/opt/isolated_container"
HOST_VETH="veth-host"
CONTAINER_VETH="veth-container"
HOST_IP="192.168.200.1/24"
CONTAINER_IP="192.168.200.2/24"
GATEWAY_IP="192.168.200.1"
SSH_PORT=22
HTTP_PORT=80
# Clean up any existing container filesystem
rm -rf "${CONTAINER_ROOT}"
mkdir -p "${CONTAINER_ROOT}"/{bin,etc/dropbear,proc,dev,www}
# Create device nodes (if not already present)
if [ ! -e "${CONTAINER_ROOT}/dev/null" ]; then
mknod -m 666 "${CONTAINER_ROOT}/dev/null" c 1 3
fi
if [ ! -e "${CONTAINER_ROOT}/dev/urandom" ]; then
mknod -m 666 "${CONTAINER_ROOT}/dev/urandom" c 1 9
fi
# Set up BusyBox in the container filesystem
cp /bin/busybox "${CONTAINER_ROOT}/bin/"
chmod +x "${CONTAINER_ROOT}/bin/busybox"
cd "${CONTAINER_ROOT}/bin"
ln -sf busybox sh
ln -sf busybox ls
ln -sf busybox mkdir
ln -sf busybox cat
ln -sf busybox echo
ln -sf busybox httpd
cd -
# Set up Dropbear in the container filesystem
cp /usr/sbin/dropbear "${CONTAINER_ROOT}/bin/dropbear"
chmod +x "${CONTAINER_ROOT}/bin/dropbear"
# Copy required libraries for dropbear (using ldd)
ldd /usr/sbin/dropbear | awk '/=>/ {print $3}' | while read -r lib; do
dest_dir="${CONTAINER_ROOT}$(dirname "$lib")"
mkdir -p "$dest_dir"
cp "$lib" "$dest_dir/"
done
# Create minimal passwd and group files
echo "root:x:0:0:root:/:/bin/sh" > "${CONTAINER_ROOT}/etc/passwd"
echo "root:x:0:" > "${CONTAINER_ROOT}/etc/group"
# Generate SSH host key if not present
if [ ! -f "${CONTAINER_ROOT}/etc/dropbear/dropbear_rsa_host_key" ]; then
dropbearkey -t rsa -f "${CONTAINER_ROOT}/etc/dropbear/dropbear_rsa_host_key"
fi
# Set up web content (for testing; remove if not needed)
echo "Isolated Container Web Server Ready!" > "${CONTAINER_ROOT}/www/index.html"
# Set up veth pair on the host
ip link add "$HOST_VETH" type veth peer name "$CONTAINER_VETH"
ip addr add "$HOST_IP" dev "$HOST_VETH"
ip link set "$HOST_VETH" up
# Launch an isolated container shell with unshare and capture its PID
CONTAINER_PID=$(unshare --fork --pid --mount --uts --ipc --net --user --map-root-user bash -c 'echo $$; exec sleep infinity')
echo "Container PID: $CONTAINER_PID"
# Move the container side of the veth pair into the container's network namespace
ip link set "$CONTAINER_VETH" netns "$CONTAINER_PID"
# Configure networking inside the container using nsenter
nsenter --target "$CONTAINER_PID" --net bash -c "
ip addr add '$CONTAINER_IP' dev $CONTAINER_VETH &&
ip link set $CONTAINER_VETH up &&
ip link set lo up &&
ip route add default via '$GATEWAY_IP'
"
# Mount proc in the container
mkdir -p "${CONTAINER_ROOT}/proc"
nsenter --target "$CONTAINER_PID" --mount bash -c "mount -t proc proc ${CONTAINER_ROOT}/proc"
# Chroot into the container and start SSH and HTTP services
ip link set lo up &&
ip route add default via '$GATEWAY_IP'
"
# Mount proc in the container
mkdir -p "${CONTAINER_ROOT}/proc"
nsenter --target "$CONTAINER_PID" --mount bash -c "mount -t proc proc ${CONTAINER_ROOT}/proc"
# Chroot into the container and start SSH and HTTP services
# Mount proc in the container
mkdir -p "${CONTAINER_ROOT}/proc"
nsenter --target "$CONTAINER_PID" --mount bash -c "mount -t proc proc ${CONTAINER_ROOT}/proc"
# Chroot into the container and start SSH and HTTP services
mkdir -p "${CONTAINER_ROOT}/proc"
nsenter --target "$CONTAINER_PID" --mount bash -c "mount -t proc proc ${CONTAINER_ROOT}/proc"
# Chroot into the container and start SSH and HTTP services
nsenter --target "$CONTAINER_PID" --mount bash -c "mount -t proc proc ${CONTAINER_ROOT}/proc"
# Chroot into the container and start SSH and HTTP services
# Chroot into the container and start SSH and HTTP services
# Chroot into the container and start SSH and HTTP services
nsenter --target "$CONTAINER_PID" --mount chroot "${CONTAINER_ROOT}" /bin/sh -c "
nsenter --target "$CONTAINER_PID" --mount chroot "${CONTAINER_ROOT}" /bin/sh -c "
echo 'Inside container: starting HTTP server and Dropbear SSH daemon...';
echo 'Inside container: starting HTTP server and Dropbear SSH daemon...';
/bin/httpd -f -p $HTTP_PORT -h /www &
exec /bin/dropbear -F -E -p $SSH_PORT
"
# End of script
Step 2: Understanding Key Components
1. Container Filesystem
-
/bin
: Contains BusyBox (provides basic Linux commands) -
/dev
: Device files likenull
andurandom
(required for programs to work) -
/etc/dropbear
: Stores SSH host keys
2. Network Setup
- Creates a virtual Ethernet pair (
host-side
andcontainer-side
) - Container gets IP
192.168.100.2
, host uses192.168.100.1
- Port
2222
on host forwards to container’s SSH port (22
)
Step 3: Running the Script
- Make it executable:
chmod +x create-container.sh
- Execute with sudo:
sudo ./create-container.sh
- Connect via SSH:
sudo nsenter --target --mount --uts --ipc --net --pid bash
OR
- Connect via SSH:
ssh root@localhost -p 2222
Step 4: What to Expect
Connection
You’ll see a warning about the SSH key fingerprint (this is normal). Type yes
to continue.
Inside the Container
Try these commands:
ls / # See container filesystem
ip addr # Show container's network
ps aux # List running processes (only container's)
Troubleshooting
1. SSH Connection Fails
iptables -t nat -L PREROUTING
2. Permission Denied
- Ensure you used
sudo
when running the script - Recreate device files if missing:
mknod -m 666 /opt/my_container/dev/null c 1 3
How It Works: Beginner’s Glossary
- Namespace: A isolated workspace for processes (like a private room)
- Veth Pair: Virtual Ethernet cable connecting host and container
- BusyBox: Swiss Army knife of Linux commands in a single file
- Dropbear: Lightweight SSH server (uses less resources than OpenSSH)
Next Steps
-
Add Persistence:
Create files in
/opt/my_container/www
. -
Customize SSH:
Add your public key to
/opt/my_container/root/.ssh/authorized_keys
. -
** Verify cgroups v2 is Active**
mount | grep cgroup2
-
Create a Custom cgroup
mkdir /sys/fs/cgroup/mycontainer -
Limit CPU Usage
echo "50000 100000" > /sys/fs/cgroup/mycontainer/cpu.max
6 Limit Memory Usage
echo $((512 * 1024 * 1024)) > /sys/fs/cgroup/mycontainer/memory.max
-
Explore More:
Try installing additional software in the container usingchroot
.
Conclusion
You’ve just created a fully isolated Linux container from scratch! While this isn’t production-ready, it demonstrates core containerization concepts used by Docker and Kubernetes. Experiment with modifying the network setup or adding new services to deepen your understanding.
Happy container hacking!