Skip to content

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

  1. Download docker-compose.yml:

    curl -fsSL https://raw.githubusercontent.com/yodamad/easylab/main/docker-compose.yml -o docker-compose.yml
    
    Or from GitLab:
    curl -fsSL https://gitlab.com/yodamad-workshops/easylab/-/raw/main/docker-compose.yml -o docker-compose.yml
    

  2. Set passwords (optional but recommended):

    export LAB_ADMIN_PASSWORD="your-secure-password"
    export LAB_STUDENT_PASSWORD="your-student-password"
    

  3. Start the application:

    docker-compose up -d
    

  4. Access the application:

  5. Main application: http://localhost:8080
  6. Health check: http://localhost:8080/health

  7. 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")
  • TRUST_FORWARDED_PROTO: Whether the X-Forwarded-Proto header from an incoming request is trusted to mark the session cookie Secure (default: true). Leave this at its default when the app sits behind a reverse proxy that sets/overwrites this header. Set to false if the app is ever reachable in a way where a client-supplied X-Forwarded-Proto header would reach the app unstripped — otherwise a client could suppress the cookie's Secure flag.
  • EASYLAB_MODE: Which areas the instance serves — all (default: admin space and student space) or admin (admin space only, for when every lab has its own in-lab student portal). The --mode flag takes precedence.
  • EASYLAB_PUBLIC_URL: This instance's address as students reach it, e.g. https://easylab.example.com. In-lab student portals send students here for Microsoft / GitHub / GitLab sign-in; without it they offer password sign-in only. This is only the default: it can also be set, and overridden, from the admin's Student portals page.
  • EASYLAB_PORTAL_IMAGE: Image the in-lab student portals run (default: docker.io/yodamad/easylab:v<this version>). A build without a version, such as make dev, has no default. Also settable from the Student portals page, which wins over this variable.
  • 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 workspace create/delete requests processed at the same time, across all labs (default: 20). Each request 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 — or an admin bulk-deleting many workspaces 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.
  • PULUMI_EXECUTION_CONCURRENCY: Maximum number of Pulumi executions (preview/up/destroy) running at the same time, across all labs (default: 5). Each execution spawns a full Pulumi engine process plus its resource provider plugins, so this bounds how much host CPU/memory a burst of concurrent lab creates, destroys, retries, or recreations can consume at once; extra executions wait briefly rather than all starting together. Raise it on a bigger host, lower it on a smaller one.
  • CLEANUP_DELETE_CONCURRENCY: Maximum number of workspace deletions run at once per job during a single automatic cleanup pass (default: 5). Separate from WORKSPACE_CREATE_CONCURRENCY since cleanup runs on a background schedule rather than in response to a request.
  • LAB_DATA_ENCRYPTION_KEY (recommended when DATA_DIR is 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 (mode 0600), 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 with openssl rand -base64 32 and 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. Setting this variable explicitly also lets EasyLab keep cloud provider credentials and DNS profiles across restarts with nothing to unlock — see Credential storage; the auto-generated key file is never used for that.
  • PULUMI_CONFIG_PASSPHRASE (recommended): passphrase Pulumi uses to encrypt secrets in local stack state. If unset it defaults to the insecure literal passphrase; 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 never persisted in job files. When a lab is destroyed, recreated, or retried, they are re-read from the credential store, so they must be available at that time: provided through the OVH_* / AZURE_* environment variables below, or saved from the admin UI with credential storage set up. Without either, credentials entered in the UI live in memory only and are lost on restart.

Azure AD student login (optional — all three required to enable):

  • AZURE_AD_CLIENT_ID: Application (client) ID of the Azure app registration
  • AZURE_AD_CLIENT_SECRET: Client secret of the Azure app registration
  • AZURE_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 files
  • lab_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

  1. Change the default admin password before deploying to production
  2. Use environment variables for sensitive configuration
  3. Limit container privileges (the container runs as non-root user)
  4. Use HTTPS in production with a reverse proxy
  5. Regularly update the Docker images

Troubleshooting

Common Issues:

  1. Port already in use:

    # Change the port in docker-compose.yml
    ports:
      - "8081:8080"
    

  2. Permission denied:

  3. Ensure Docker daemon is running
  4. Check that you have Docker permissions

  5. Data not persisting:

  6. Verify volumes are created: docker volume ls
  7. Check volume mounts in docker-compose ps

  8. 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:

  1. Use a reverse proxy (nginx, traefik) with SSL termination
  2. Set up monitoring (Prometheus, Grafana)
  3. Configure log aggregation (ELK stack, Loki)
  4. Implement backup strategies for the data volumes
  5. Use Docker secrets for sensitive configuration
  6. 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:

  1. Check the application logs: docker-compose logs easylab
  2. Verify Docker and Docker Compose versions
  3. Ensure all prerequisites are met
  4. Check the main README.md for application-specific configuration