Docker Deployment Guide
This guide explains how to deploy the EasyLab application using Docker and Docker Compose.
Prerequisites
- Docker and Docker Compose installed on your system
- At least 2GB of available RAM for the container
Quick Start
-
Download docker-compose.yml:
Or from GitLab:curl -fsSL https://raw.githubusercontent.com/yodamad/easylab/main/docker-compose.yml -o docker-compose.ymlcurl -fsSL https://gitlab.com/yodamad-workshops/easylab/-/raw/main/docker-compose.yml -o docker-compose.yml -
Set passwords (optional but recommended):
export LAB_ADMIN_PASSWORD="your-secure-password" export LAB_STUDENT_PASSWORD="your-student-password" -
Start the application:
docker-compose up -d -
Access the application:
- Main application: http://localhost:8080
-
Health check: http://localhost:8080/health
-
View logs:
docker-compose logs -f easylab
Configuration
Environment Variables
The application can be configured using the following environment variables:
LAB_ADMIN_PASSWORD: Admin password for the web interface (default: "admin123")LAB_STUDENT_PASSWORD: Student password for the web interface (default: "student123")PORT: Port to run the application on (default: 8080)WORK_DIR: Directory for job workspaces (default: /app/jobs)DATA_DIR: Directory for persisting job data (default: /app/data)CLEANUP_INTERVAL_MINUTES: How often (in minutes) the cleanup service checks for expired workspaces and scheduled lab deletions (default: 5)WORKSPACE_CREATE_CONCURRENCY: Maximum number of student workspace-creation requests processed at the same time, across all labs (default: 20). Each creation makes several sequential calls to the lab's Kubernetes API server, so this bounds how hard a burst of students starting workspaces together — e.g. at the start of a large workshop — can hit that cluster at once; extra requests wait briefly rather than firing all together. Raise it for a bigger/faster cluster, lower it for a smaller one.LAB_DATA_ENCRYPTION_KEY(recommended whenDATA_DIRis set): base64-encoded 32-byte key (AES-256) used to encrypt secrets at rest in persisted job files — cluster kubeconfigs and DNS provider credentials. If unset while a data directory is configured, the server generates a random key on first start and saves it to<DATA_DIR>/.encryption_key(mode0600), reusing it on subsequent restarts so persisted data stays decryptable; a startup log line reports which path was used. For production, set this variable explicitly from a managed secret store instead of relying on the generated file. Generate one withopenssl rand -base64 32and keep it stable: rotating the key (or losing the generated file) makes previously-encrypted kubeconfigs unreadable, so you must destroy/recreate affected labs after a change.PULUMI_CONFIG_PASSPHRASE(recommended): passphrase Pulumi uses to encrypt secrets in local stack state. If unset it defaults to the insecure literalpassphrase; set a strong value. The server logs a[SECURITY]warning at startup while the default is in use (existing stacks stay decryptable).
Provider credentials are not written to lab state
OVHcloud and Azure API credentials are held in memory only (from the environment variables below or the admin UI) and are never persisted in job files. When a lab is destroyed, recreated, or retried, the credentials are re-read from the in-memory store, so they must be available (typically via the OVH_* / AZURE_* environment variables) at that time.
Azure AD student login (optional — all three required to enable):
AZURE_AD_CLIENT_ID: Application (client) ID of the Azure app registrationAZURE_AD_CLIENT_SECRET: Client secret of the Azure app registrationAZURE_AD_TENANT_ID: Directory (tenant) ID
See Admin guide — Azure AD student authentication for the full setup steps.
You can also set OVHcloud credentials via environment variables (OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY, OVH_SERVICE_NAME, OVH_ENDPOINT); see OVHcloud configuration.
Environment file
When running the EasyLab server binary directly (not only in Docker), you can load environment variables from a file at startup:
./easylab-server -env-file=./env.sh
The file format is standard .env style: one KEY=VALUE per line. Lines starting with # are comments; lines starting with export are supported for compatibility with shell scripts. This allows you to keep secrets out of the command line and reuse a single file for local development.
Server flags (when running the binary): -port (default: 8081), -work-dir, -data-dir, -env-file. Environment variables WORK_DIR and DATA_DIR override the defaults if set.
Data Persistence
The Docker Compose setup includes two named volumes for data persistence:
lab_jobs: Stores job workspaces and temporary fileslab_data: Stores application data, job metadata, and configurations
Important: These volumes ensure that your lab deployments and application data survive container restarts and updates.
Docker Commands
Build the image manually:
docker build -t easylab .
Multi-architecture (amd64 + arm64): CI publishes each tag as linux/amd64 and linux/arm64 so the same tag works on typical cloud nodes and on arm64 (for example AWS Graviton). The GitLab pipeline builds both architectures with GoReleaser and passes --build-arg SKIP_BUILD=true so each image layer copies the matching prebuilt binary from docker-prebuild/linux_${TARGETARCH}/main.
Local multi-arch with Buildx (compile inside Docker per platform; default SKIP_BUILD=false):
docker buildx build --platform linux/amd64,linux/arm64 --build-arg SKIP_BUILD=false -t your-registry/easylab:tag --push .
Local multi-arch reusing prebuilt binaries (same layout as CI: run goreleaser build --clean --snapshot -f .goreleaser.gitlab.yaml, then copy each dist/.../main into docker-prebuild/linux_amd64/main and docker-prebuild/linux_arm64/main as in .gitlab-ci.yml, or use your own paths and adjust the Dockerfile):
docker buildx build --platform linux/amd64,linux/arm64 --build-arg SKIP_BUILD=true -t your-registry/easylab:tag --push .
For a single platform into the local Docker daemon, use one platform and --load, for example --platform linux/amd64.
Run with Docker Compose:
# Start in detached mode
docker-compose up -d
# Start and view logs
docker-compose up
# Stop the application
docker-compose down
# Rebuild and restart
docker-compose up -d --build
Run with Docker directly:
# Create volumes first
docker volume create lab_jobs
docker volume create lab_data
# Run the container
docker run -d \
--name easylab \
-p 8080:8080 \
-v lab_jobs:/app/jobs \
-v lab_data:/app/data \
-e LAB_ADMIN_PASSWORD="your-password" \
-e LAB_STUDENT_PASSWORD="your-student-password" \
yodamad/easylab:v0.2.0
Data Management
Inspect volumes:
# List volumes
docker volume ls | grep lab
# Inspect volume details
docker volume inspect lab_data
Clean up:
# Stop and remove containers
docker-compose down
# Remove volumes (WARNING: This deletes all data!)
docker volume rm lab_jobs lab_data
# Remove images
docker rmi yodamad/easylab:v0.2.0
Health Monitoring
The application includes built-in health checks:
- Health endpoint:
GET /health - Docker health check runs every 30 seconds
- Container will restart automatically if unhealthy
Monitor the health status:
docker ps
docker-compose ps
Security Considerations
- Change the default admin password before deploying to production
- Use environment variables for sensitive configuration
- Limit container privileges (the container runs as non-root user)
- Use HTTPS in production with a reverse proxy
- Regularly update the Docker images
Troubleshooting
Common Issues:
-
Port already in use:
# Change the port in docker-compose.yml ports: - "8081:8080" -
Permission denied:
- Ensure Docker daemon is running
-
Check that you have Docker permissions
-
Data not persisting:
- Verify volumes are created:
docker volume ls -
Check volume mounts in
docker-compose ps -
Application not starting:
# Check logs docker-compose logs easylab # Check health curl http://localhost:8080/health
Logs and Debugging:
# View application logs
docker-compose logs -f easylab
# Enter container for debugging
docker-compose exec easylab sh
# Check running processes
docker-compose exec easylab ps aux
Production Deployment
For production deployment, consider:
- Use a reverse proxy (nginx, traefik) with SSL termination
- Set up monitoring (Prometheus, Grafana)
- Configure log aggregation (ELK stack, Loki)
- Implement backup strategies for the data volumes
- Use Docker secrets for sensitive configuration
- Set resource limits in docker-compose.yml
Example production docker-compose.yml additions:
services:
easylab:
deploy:
resources:
limits:
memory: 1G
reservations:
memory: 512M
restart: always
Support
If you encounter issues:
- Check the application logs:
docker-compose logs easylab - Verify Docker and Docker Compose versions
- Ensure all prerequisites are met
- Check the main README.md for application-specific configuration