-
Notifications
You must be signed in to change notification settings - Fork 29
Container Lifecycle
This guide provides detailed technical documentation about how Flowcase manages Docker containers throughout their lifecycle, from creation to destruction.
Flowcase containers go through several distinct phases during their lifetime:
- Resource Validation - Check system resources before creation
- Image Verification - Ensure container images are available
- Container Creation - Create and configure the Docker container
- Network Configuration - Set up nginx proxy routing
- Runtime Management - Monitor and manage running containers
- Cleanup and Destruction - Proper container removal
[!INFO] Implementation Status: This documentation reflects the current implementation in Flowcase. Features marked as "planned" or "future" are not yet available.
Before creating any container, Flowcase performs resource validation to prevent system overload:
# From routes/droplet.py - actual implementation
total_allocated_memory = 0
total_allocated_cores = 0
# Check all existing instances
for instance in instances:
instance_droplet = droplet_dict.get(instance.droplet_id)
if instance_droplet:
total_allocated_cores += instance_droplet.container_cores
total_allocated_memory += instance_droplet.container_memory
# System resource limits
system_cores = os.cpu_count()
total_memory = psutil.virtual_memory().total / 1024 / 1024 # MB
# Safety margins and oversubscription policies
max_allowed_memory = total_memory * 0.85 # 85% of total memory
max_allowed_cores = system_cores * 2.0 # 2x CPU oversubscription| Resource | Policy | Reasoning |
|---|---|---|
| Memory | 85% of total RAM | Leave 15% for system operations |
| CPU | 2x oversubscription | Containers share CPU efficiently |
| Disk | No limit (monitored) | Dynamic based on available space |
| Network | Shared bridge | Isolated container network |
Flowcase handles two types of droplets differently:
Container Droplets (desktop environments, applications):
- Full resource validation
- Memory and CPU limits enforced
- Persistent storage support
- VNC-based streaming
Guacamole Droplets (remote connections via VNC/RDP/SSH):
- No resource checks (lightweight proxy)
- Minimal resource usage
- Connection to external systems
- Built-in Guacamole protocol handling
All Flowcase containers use a predictable naming pattern:
flowcase_generated_{instance_id}
Where instance_id is a UUID generated for each droplet instance.
# Check if required image exists locally
image_exists = False
for image in docker_client.images.list():
if image_name in image.tags:
image_exists = True
break
if not image_exists:
return error("Docker image not found. Image might still be downloading.")# Handle persistent profiles if configured
if droplet.container_persistent_profile_path:
profilePath = droplet.container_persistent_profile_path
# Variable substitution
profilePath = profilePath.replace("{user_id}", str(current_user.id))
profilePath = profilePath.replace("{username}", current_user.username)
profilePath = profilePath.replace("{droplet_id}", str(droplet_id))
mount = docker.types.Mount(
target="/home/flowcase-user",
source=profilePath,
type="bind",
consistency="[r]private"
)Standard Container Droplets:
container = docker_client.containers.run(
image=image_name,
name=f"flowcase_generated_{instance.id}",
environment={
"DISPLAY": ":1",
"VNC_PW": current_user.auth_token,
"VNC_RESOLUTION": resolution
},
detach=True,
network="flowcase_default_network",
mem_limit=f"{droplet.container_memory}000000", # MB to bytes
cpu_shares=int(droplet.container_cores * 1024), # CPU shares
mounts=[mount] if mount else None,
)Guacamole Droplets:
container = docker_client.containers.run(
image=f"flowcaseweb/flowcase-guac:{__version__}",
name=f"flowcase_generated_{instance.id}",
environment={"GUAC_KEY": current_user.auth_token[:32]},
detach=True,
network="flowcase_default_network",
)After container creation, Flowcase automatically configures nginx routing:
container = docker_client.containers.get(f"flowcase_generated_{instance.id}")
ip = container.attrs['NetworkSettings']['Networks']['flowcase_default_network']['IPAddress']authHeader = base64.b64encode(
b'flowcase_user:' + current_user.auth_token.encode()
).decode('utf-8')For Container Droplets:
location /desktop/{instance_id}/vnc/ {
auth_request /droplet_connect;
proxy_pass https://{container_ip}:6901/;
proxy_set_header Authorization "Basic {authHeader}";
}
location /desktop/{instance_id}/vnc/websockify {
auth_request /droplet_connect;
proxy_pass https://{container_ip}:6901/websockify/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_read_timeout 86400s;
proxy_buffering off;
}
location /desktop/{instance_id}/uploads/ {
auth_request /droplet_connect;
proxy_pass https://{container_ip}:4902/;
proxy_set_header Authorization "Basic {authHeader}";
}For Guacamole Droplets:
location /desktop/{instance_id}/vnc/ {
auth_request /droplet_connect;
proxy_pass http://{container_ip}:8080/;
}
location /desktop/{instance_id}/vnc/websockify {
auth_request /droplet_connect;
proxy_pass http://{container_ip}:8080/websockify/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}nginx_container = docker_client.containers.get("flowcase-nginx")
result = nginx_container.exec_run("nginx -s reload")| Service | Container Port | Purpose |
|---|---|---|
| VNC | 6901 | Desktop streaming (HTTPS) |
| Audio | 4901 | Audio streaming |
| File Upload | 4902 | File management |
| Guacamole | 8080 | Protocol gateway |
Flowcase monitors containers through several mechanisms:
# DropletInstance model tracks:
- instance.id (UUID)
- instance.droplet_id (foreign key)
- instance.user_id (foreign key)
- instance.created_at (timestamp)
- instance.updated_at (timestamp)# Admin panel checks container status
try:
container = docker_client.containers.get(f"flowcase_generated_{instance.id}")
status = container.status
# Container found and status available
except docker.errors.NotFound:
# Container no longer exists
# Instance should be cleaned up# Available through docker stats
docker stats --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}"# From utils/docker.py - cleanup_containers()
containers = docker_client.containers.list(all=True)
for container in containers:
regex = re.compile(r"flowcase_generated_([a-z0-9]+(-[a-z0-9]+)+)", re.IGNORECASE)
if regex.match(container.name):
container.stop()
container.remove()When a user destroys an instance through the UI:
if instance.user_id != current_user.id:
return jsonify({"success": False, "error": "Unauthorized"}), 403try:
container = docker_client.containers.get(f"flowcase_generated_{instance.id}")
container.remove(force=True) # Force removal even if running
except Exception as e:
log("ERROR", f"Error removing container: {str(e)}")config_path = f"/flowcase/nginx/containers.d/{instance.id}.conf"
if os.path.exists(config_path):
os.remove(config_path)db.session.delete(instance)
db.session.commit()Administrators can force-destroy any instance:
# From routes/admin.py - api_admin_delete_instance()
# Similar process but without user ownership checkFlowcase automatically manages container images:
# From utils/docker.py - pull_images()
def pull_images():
droplets = Droplet.query.all()
# Add Guacamole image to pull list
droplets.append(Droplet(
container_docker_registry="https://index.docker.io/v1/",
container_docker_image="flowcaseweb/flowcase-guac:" + __version__
))
for droplet in droplets:
if droplet.container_docker_registry and "docker.io" not in droplet.container_docker_registry:
image = f"{registry}/{droplet.container_docker_image}"
else:
image = droplet.container_docker_image
try:
docker_client.images.pull(image)
except Exception as e:
log("ERROR", f"Error pulling image {image}: {e}")- Docker Hub: Default registry
- Private Registries: Custom registry URLs supported
-
Flowcase Registry: Default at
https://registry.flowcase.org
# Check if image exists before creating container
image_exists = False
for image in docker_client.images.list():
if image_name in image.tags:
image_exists = True
break- Container filesystem is temporary
- Data lost when container is destroyed
- Faster startup times
- No configuration required
# Configuration in droplet settings
container_persistent_profile_path = "/data/users/{user_id}/{droplet_id}"
# Variable substitution:
# {user_id} -> User's UUID
# {username} -> User's username
# {droplet_id} -> Droplet's UUIDImportant
Storage Default: All containers use ephemeral storage by default. Persistent storage must be explicitly configured per droplet by administrators and requires proper host directory setup.
mount = docker.types.Mount(
target="/home/flowcase-user", # Inside container
source=profilePath, # Host system path
type="bind", # Bind mount type
consistency="[r]private" # Read-only private consistency
)Warning
Host Requirements: Persistent storage requires the host directory to exist and be accessible by the Docker daemon. Incorrect permissions will prevent container startup.
First-time persistent storage setup includes a "warm-up" container:
# Create temporary container to initialize storage
if not os.path.exists(profilePath + ".bashrc"):
container = docker_client.containers.run(
image=image_name,
detach=True,
mem_limit="512000000", # 512MB for init
cpu_shares=int(droplet.container_cores * 1024),
mounts=[mount],
)
time.sleep(1)
container.stop()
container.remove(force=True)Tip
Initialization Process: The warm-up container ensures proper file permissions and directory structure for persistent storage before the actual user container starts.
Common failure scenarios and handling:
if projected_memory_usage > max_allowed_memory:
log("ERROR", f"Insufficient memory for user {current_user.username}")
return jsonify({"success": False, "error": "Insufficient memory"}), 400if not image_exists:
log("WARNING", f"Docker image {droplet.container_docker_image} not found")
return jsonify({"success": False, "error": "Docker image not found"}), 400if not utils.docker.docker_client:
log("ERROR", "Docker client not available")
return jsonify({"success": False, "error": "Docker service unavailable"}), 500try:
nginx_container.exec_run("nginx -s reload")
except Exception as e:
log("ERROR", f"Error reloading Nginx configuration: {str(e)}")
# Container created but networking may not workFlowcase implements comprehensive logging for container operations:
# Creation logging
log("INFO", f"Creating new instance for user {current_user.username} with droplet {droplet.display_name}")
log("INFO", f"Instance created for user {current_user.username} with droplet {droplet.display_name}")
# Error logging
log("ERROR", f"Insufficient memory for user {current_user.username} to request droplet {droplet.display_name}")
log("WARNING", f"Docker image {droplet.container_docker_image} not found")
log("ERROR", f"Error removing container: {str(e)}")- Database Logs: Available through Admin Panel → Logs
-
Container Logs:
docker logs flowcase_generated_{instance_id} -
System Logs:
docker compose logs -f web
# Check container logs
docker logs flowcase_generated_{instance_id}
# Check resource availability
free -h
df -h
# Verify image exists
docker images | grep {image_name}# Check container network
docker inspect flowcase_generated_{instance_id} | grep NetworkMode
# Verify nginx configuration
ls -la nginx/containers.d/{instance_id}.conf
# Test nginx reload
docker exec flowcase-nginx nginx -t# Check mount permissions
ls -la /path/to/persistent/storage
# Verify bind mount in container
docker exec flowcase_generated_{instance_id} mount | grep flowcase-userThis container lifecycle documentation reflects the actual implementation in Flowcase and provides administrators with detailed understanding of how containers are managed throughout their lifetime.