Setting up Isolated Developer Workspaces on RHEL 9 using Podman Containers and Nginx Stream Router Routing
Prerequisites and Initial Setup
This guide assumes a RHEL 9 system with root or sudo privileges. We’ll be leveraging Podman for containerization and Nginx as a stream router to direct traffic to isolated developer environments. Ensure Podman is installed and running. If not, execute:
sudo dnf install -y podmansudo systemctl enable --now podman.socket
Similarly, Nginx must be installed. For this setup, we’ll use Nginx as a TCP stream proxy, not for HTTP. Install it with:
sudo dnf install -y nginxsudo systemctl enable --now nginx
Defining Developer Environment Images
We’ll create a simple Dockerfile to build a base image for our developer workspaces. This image will include common tools and a basic application server (e.g., a Python Flask app). For demonstration, let’s assume each developer needs a Python 3.10 environment with Flask and a simple web service.
Create a file named Dockerfile.dev:
mkdir ~/dev-env-image && cd ~/dev-env-image
Dockerfile.dev content:
# Use a minimal RHEL 9 base image
FROM registry.access.redhat.com/ubi9/ubi:latest
# Install Python 3.10 and pip
RUN dnf update -y && \
dnf install -y python310 python310-pip && \
dnf clean all
# Set Python 3.10 as the default
RUN alternatives --set python /usr/bin/python3.10
# Install Flask
RUN pip3 install Flask
# Copy a sample application (optional, for testing)
# Create a simple app.py in the same directory as Dockerfile.dev
COPY app.py /app/app.py
# Expose the application port
EXPOSE 5000
# Set the working directory
WORKDIR /app
# Command to run the application
CMD ["python3", "app.py"]
Create a sample app.py in the same directory:
from flask import Flask
import os
app = Flask(__name__)
@app.route('/')
def hello():
hostname = os.uname().nodename
return f"Hello from container {hostname}!\n"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Build the container image:
sudo podman build -t dev-workspace-base:latest -f Dockerfile.dev .
Creating Isolated Developer Workspaces
For each developer, we’ll launch a container from our base image. We’ll assign a unique port for each developer’s application and map it to a host port. This allows multiple developers to run their environments on the same host without port conflicts.
Let’s define a script to launch a developer’s workspace. This script will take a developer ID and a desired host port as arguments.
Create a script named launch_dev_workspace.sh:
#!/bin/bash
if [ "$#" -ne 2 ]; then
echo "Usage: $0 <developer_id> <host_port>"
exit 1
fi
DEV_ID="$1"
HOST_PORT="$2"
CONTAINER_NAME="dev-workspace-${DEV_ID}"
APP_PORT="5000" # The port the application listens on inside the container
echo "Launching workspace for developer: ${DEV_ID}"
echo "Mapping host port ${HOST_PORT} to container port ${APP_PORT}"
# Check if container is already running
if sudo podman ps -q --filter "name=${CONTAINER_NAME}" --format '{{.Names}}' | grep -q "${CONTAINER_NAME}"; then
echo "Container ${CONTAINER_NAME} is already running. Skipping launch."
exit 0
fi
# Run the container
sudo podman run -d \
--name "${CONTAINER_NAME}" \
-p "${HOST_PORT}:${APP_PORT}" \
--restart always \
dev-workspace-base:latest
if [ $? -eq 0 ]; then
echo "Workspace ${CONTAINER_NAME} launched successfully."
echo "Access it via http://<your_server_ip>:${HOST_PORT}"
else
echo "Failed to launch workspace ${CONTAINER_NAME}."
exit 1
fi
Make the script executable:
chmod +x launch_dev_workspace.sh
Now, you can launch workspaces for different developers. For example, for ‘alice’ on port 8081 and ‘bob’ on port 8082:
sudo ./launch_dev_workspace.sh alice 8081 sudo ./launch_dev_workspace.sh bob 8082
Configuring Nginx as a Stream Router
Instead of directly exposing developer ports, we can use Nginx’s stream module to route traffic based on a subdomain or a specific port. This provides a more centralized and manageable access point. For this example, we’ll configure Nginx to route traffic based on the destination port.
First, ensure the stream module is enabled in Nginx. It’s usually compiled in by default on RHEL packages. We need to add a stream block to the Nginx configuration.
Edit the main Nginx configuration file, typically /etc/nginx/nginx.conf. Add the following stream block outside of the http block:
# /etc/nginx/nginx.conf
# ... other global directives ...
# Stream block for TCP/UDP proxying
stream {
# Define upstream servers for each developer workspace
# These map a logical name to the actual host:port of the container
upstream dev_alice {
server 127.0.0.1:8081; # Assuming Nginx is on the same host as containers
}
upstream dev_bob {
server 127.0.0.1:8082;
}
# Define server blocks for routing
# This server listens on port 80 and routes based on the target port
# This is a simplified example; a more robust solution might use SNI or specific hostnames
server {
listen 80;
proxy_pass dev_alice; # Default to Alice if no other rule matches
proxy_timeout 10s;
proxy_connect_timeout 1s;
}
# Example: Route traffic to Bob's workspace if it arrives on port 8082
# This requires clients to connect to Nginx on different ports for different devs
# A more common approach is using subdomains with DNS.
server {
listen 8082; # Listening on a different port for Bob
proxy_pass dev_bob;
proxy_timeout 10s;
proxy_connect_timeout 1s;
}
# If you want to route based on subdomains (e.g., alice.dev.example.com),
# you would typically use DNS to point subdomains to your Nginx server's IP
# and then configure Nginx's http block to proxy to the stream block,
# or use a more advanced stream configuration if available/needed.
# For simplicity here, we'll stick to port-based routing or direct access.
# For direct access to Alice's workspace on port 8081
server {
listen 8081;
proxy_pass dev_alice;
proxy_timeout 10s;
proxy_connect_timeout 1s;
}
}
# ... http block ...
Important Note on Stream Routing: The Nginx stream module operates at Layer 4 (TCP/UDP). It doesn’t inspect HTTP headers. Therefore, routing based on hostnames (like subdomains) requires either:
- DNS configuration pointing subdomains (e.g.,
alice.dev.example.com) to your Nginx server’s IP, and then using Nginx’shttpblock to proxy to the stream block, or directly to the container ports if not using stream. - A more complex stream configuration that might involve custom logic or specific protocols if not using standard HTTP.
For simplicity in this example, we’ve shown direct port mapping in the stream block. A more practical setup for multiple developers would involve DNS and potentially a single entry point (e.g., port 80/443) that Nginx then routes based on the requested hostname.
After modifying nginx.conf, test the configuration and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
Accessing and Managing Workspaces
With the Nginx stream router configured, developers can access their environments. If using direct port mapping (as in the simplified Nginx config above), they would connect to <your_server_ip>:<their_assigned_port>. For example, Alice would access http://<your_server_ip>:8081.
To stop or remove a developer’s workspace:
# Stop a container sudo podman stop dev-workspace-alice # Remove a container sudo podman rm dev-workspace-alice # List running containers sudo podman ps # List all containers (including stopped) sudo podman ps -a
Advanced Considerations and Best Practices
Security: Exposing developer environments directly, even through a router, requires careful consideration. Implement network segmentation, firewall rules, and potentially authentication mechanisms if these environments are accessible externally. For internal development, ensure the host system is secured.
Resource Management: Each container consumes CPU, memory, and disk I/O. Monitor resource usage on the host and consider setting resource limits for Podman containers using the --memory, --cpus, and other options in podman run.
Persistent Storage: The current setup loses any data within the container when it’s removed. For development that requires persistent data (e.g., databases, code changes not committed), use Podman volumes:
# Example with a volume for code
sudo podman run -d \
--name dev-workspace-alice \
-p 8081:5000 \
--restart always \
-v /path/on/host/for/alice/code:/app \
dev-workspace-base:latest
Dynamic Port Allocation: Instead of manually assigning ports, you could implement a system that dynamically assigns the next available high port to new developers. This would involve scripting to check /proc/net/tcp or using tools like ss.
Configuration Management: For managing multiple developer environments and their configurations, consider using tools like Ansible to automate the deployment of containers and Nginx configurations.
Nginx Subdomain Routing: A more scalable approach for Nginx would involve:
- Configuring DNS to point
dev1.yourdomain.com,dev2.yourdomain.com, etc., to your Nginx server’s IP. - Modifying the Nginx
httpblock to proxy requests to the appropriate container’s IP and port, or to the stream block if necessary. This often involves usingmapdirectives orserver_nameinhttpblocks to determine the target upstream.
This setup provides a robust foundation for isolated developer workspaces on RHEL 9, leveraging Podman for containerization and Nginx for flexible network routing.
Leave a Reply
You must be logged in to post a comment.