A cookiecutter template for creating production-ready Open edX hosting clusters with automated instance management using GitOps principles.
This project provides a complete solution for hosting multiple Open edX instances on Kubernetes clusters with:
- Automated Infrastructure: Terraform modules for AWS and DigitalOcean
- GitOps Workflows: ArgoCD and Argo Workflows for declarative deployments
- Instance Management: GitHub Actions for automated instance lifecycle
- Multi-tenancy: Secure RBAC and namespace isolation
- Scalability: Support for multiple instances per cluster
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ GitHub │ │ Kubernetes │ │ ArgoCD │
│ Actions │───▶│ Cluster │───▶│ + Workflows │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Instance │ │ Namespace │ │ Open edX │
│ Lifecycle │ │ Management │ │ Instances │
│ Management │ │ + RBAC │ │ Deployment │
└─────────────────┘ └──────────────────┘ └─────────────────┘
phd-cluster-template/
├── cluster-template/ # Cookiecutter template for clusters
│ ├── phd-{{cookiecutter.cluster_slug_normalized}}-cluster/
│ │ ├── activate # Cluster activation script
│ │ ├── infrastructure/ # Terraform infrastructure modules (after generation)
│ │ └── instances/ # Instance configurations
│ └── hooks/ # Cookiecutter post-generation hooks
├── instance-template/ # Cookiecutter template for instances
│ └── {{cookiecutter.instance_slug}}/
│ ├── application.yml # ArgoCD Application manifest
│ └── config.yml # Instance configuration
├── manifests/ # Kubernetes manifests
│ ├── argocd-*.yml # ArgoCD configuration
│ ├── argo-*.yml # Argo Workflows configuration
│ ├── openedx-*.yml # Open edX RBAC
│ └── phd-*-template.yml # Provision/deprovision workflows
├── tooling/ # Python CLI package
│ ├── phd/ # Source code
│ │ ├── cli/ # CLI commands
│ │ ├── config.py # Configuration management
│ │ ├── kubernetes.py # Kubernetes client
│ │ ├── password.py # Password utilities
│ │ └── utils.py # Utility functions
│ ├── tests/ # Test suite
│ ├── pyproject.toml # Project metadata and dependencies
│ └── uv.lock # Lock file
├── .github/workflows/ # GitHub Actions workflows
│ ├── create-instance.yml # Instance creation workflow
└ └── delete-instance.yml # Instance deletion workflow
The PHD CLI provides Python-based commands for managing clusters and instances.
Install uv (if not already installed):
curl -LsSf https://astral.sh/uv/install.sh | sh
Install PHD as a tool:
# Install from git repository
uv tool install git+https://github.com/open-craft/phd-cluster-template.git#subdirectory=tooling
# Set required environment variable
export PHD_CLUSTER_DOMAIN="your-cluster-domain.com"
# Verify installation
phd_install_argo --help
Install for contribution:
# Clone the repository
git clone https://github.com/open-craft/phd-cluster-template.git
cd phd-cluster-template/tooling
# Install with all dependencies
uv sync --all-groups
# Set required environment variable
export PHD_CLUSTER_DOMAIN="your-cluster-domain.com"
# Verify installation
phd_install_argo --help
Using PHD CLI:
# Set required environment variable
export PHD_CLUSTER_DOMAIN="example.com"
# Create cluster configuration
phd_create_cluster "PHD Production Cluster" "prod.example.com"
Using cookiecutter directly:
# Install cookiecutter
pip install cookiecutter
# Generate cluster template
cookiecutter https://github.com/open-craft/phd-cluster-template.git --directory cluster-template
# Navigate to your cluster's directory
cd your-cluster-name
# Navigate to the infrastructure directory
cd infrastructure
# Set up backend credentials via environment variables
# For AWS S3 backend:
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
# For DigitalOcean Spaces backend:
export AWS_ACCESS_KEY_ID="your-spaces-access-key"
export AWS_SECRET_ACCESS_KEY="your-spaces-secret-key"
# Deploy infrastructure
tofu init
tofu plan
tofu apply
# Set required environment variable
export PHD_CLUSTER_DOMAIN="your-cluster-domain.com"
# Install both ArgoCD and Argo Workflows
phd_install_argo
# Or install selectively
phd_install_argo --argocd-only
phd_install_argo --workflows-only
The PHD commands will generate application.yml
files for every instance with a project set to phd-{{ environment }}
, so different instance environments within the same cluster can use different project or repository settings.
However, this means one have to configure each and every phd-{{ environment }}
with repository settings per environment. For example, if the instance has the project: phd-production
set, a phd-production
project must exist in ArgoCD as well.
This is a manual work.
Using PHD CLI:
# Create instance with default configuration
phd_create_instance my-instance \
"https://github.com/your-org/your-cluster.git" \
"My Open edX Platform"
# Create instance with custom Open edX version
phd_create_instance my-instance \
"https://github.com/your-org/your-cluster.git" \
"My Open edX Platform" \
--edx-platform-repository "https://github.com/openedx/edx-platform.git" \
--edx-platform-version "release/teak" \
--tutor-version "v20.0.1"
Using GitHub Actions:
- Go to your cluster repository's Actions tab
- Select "Create Instance" workflow
- Provide instance configuration
- Monitor the workflow execution
Using PHD CLI:
# Delete instance
phd_delete_instance my-instance
Using GitHub Actions:
- Go to your cluster repository's Actions tab
- Select "Delete Instance" workflow
- Provide instance name
- Monitor the cleanup process
The project provides a comprehensive set of commands through the Python CLI:
Cluster Management:
# Create a new cluster configuration
phd_create_cluster <cluster_name> <cluster_domain> [options]
GitOps Tools:
# Install ArgoCD and Argo Workflows
phd_install_argo # Install both
phd_install_argo --argocd-only # Install ArgoCD only
phd_install_argo --workflows-only # Install Argo Workflows only
User Management:
# Create Argo user with access to both ArgoCD and Argo Workflows
phd_create_argo_user <username> [--role admin|developer|readonly] [--password PASSWORD]
# Update user permissions
phd_update_argo_user <username> --role admin|developer|readonly
# Delete Argo user
phd_delete_argo_user <username>
Instance Management:
# Create instance
phd_create_instance <name> <template_repository> <platform_name> [options]
# Delete instance
phd_delete_instance <name>
Configuration:
All commands can be configured via environment variables. See tooling/phd/config.py
for available options:
# Required
export PHD_CLUSTER_DOMAIN="your-cluster-domain.com"
# Optional (with defaults shown)
export PHD_ARGOCD_VERSION="stable"
export PHD_ARGO_WORKFLOWS_VERSION="stable"
export PHD_OPENCRAFT_MANIFESTS_URL="https://raw.githubusercontent.com/open-craft/phd-cluster-template/main/manifests"
The PHD CLI includes kubeconfig detection and setup:
Detection Order:
- Terraform/OpenTofu Output: Checks for
tofu
orterraform
commands and retrieves thekubeconfig
output - Environment Variable: Uses
KUBECONFIG_CONTENT
(supports both base64-encoded and plain text) - Existing Configuration: Falls back to
~/.kube/config
if present
Local Development:
# Use Terraform/OpenTofu
cd your-cluster/infrastructure
tofu apply # or terraform apply
cd ../..
phd_create_instance [...] # Automatically uses Terraform output
CI/CD Environments:
The CLI automatically handles base64-encoded kubeconfig in CI/CD:
env:
KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG_CONTENT }}
run: phd_create_instance ...
Error Handling:
If no kubeconfig can be found, the CLI provides a helpful error message:
No kubeconfig available. Please ensure one of the following:
1. Run this command from a directory with infrastructure directory present
2. Set KUBECONFIG_CONTENT environment variable
3. Have a valid kubeconfig at ~/.kube/config
- Namespace Isolation: Each instance runs in its own namespace
- RBAC: Proper role-based access control for all resources
- Secret Management: Secure handling of database credentials
- Network Policies: Isolated network access between instances
The system supports three user roles with different permission levels:
- ArgoCD: Full access to all applications and projects
- Argo Workflows: Full cluster-wide access to all workflow resources
- Permissions: Create, read, update, delete all workflows, templates, and cron workflows
- ArgoCD: Access to assigned applications and projects
- Argo Workflows: Full cluster-wide access to workflow resources
- Permissions: Create, read, update, delete workflows, templates, and cron workflows
- ArgoCD: Read-only access to applications and projects
- Argo Workflows: Read-only access to workflow resources
- Permissions: View workflows, templates, and cron workflows only
If you need to update a user's permissions after creation:
# Update user to admin role
phd_update_argo_user_permissions username admin
# Update user to developer role
phd_update_argo_user_permissions username developer
# Update user to readonly role
phd_update_argo_user_permissions username readonly
User roles are defined in separate YAML manifest files in the manifests/
directory:
argo-user-admin-role.yml
: Admin role and cluster role definitionsargo-user-developer-role.yml
: Developer role and cluster role definitionsargo-user-readonly-role.yml
: Readonly role and cluster role definitionsargo-user-bindings.yml
: Role and cluster role bindingsargo-user-token-secret.yml
: Service account token secret
These manifests use template variables ({{PHD_ARGO_USERNAME}}
, {{PHD_ARGO_ROLE}}
) and are applied using the __phd_kubectl_apply_from_url
function, following the established pattern for remote manifest management.
Instance workflows are defined in separate YAML manifest files in the manifests/
directory:
Provision Workflows:
phd-mysql-provision-workflow.yml
: MySQL database provision workflowphd-mongodb-provision-workflow.yml
: MongoDB database provision workflowphd-storage-provision-workflow.yml
: Storage bucket provision workflow
Deprovision Workflows:
phd-mysql-deprovision-workflow.yml
: MySQL database deprovision workflowphd-mongodb-deprovision-workflow.yml
: MongoDB database deprovision workflowphd-storage-deprovision-workflow.yml
: Storage bucket deprovision workflow
These manifests use template variables with the PHD_INSTANCE_
prefix (e.g., {{PHD_INSTANCE_NAME}}
, {{PHD_INSTANCE_MYSQL_DATABASE}}
) and are applied using the __phd_kubectl_apply_from_url
function, following the established pattern for remote manifest management.
- Kubernetes: DigitalOcean Kubernetes (DOKS)
- Databases: Managed MySQL and MongoDB
- Storage: DigitalOcean Spaces
- Networking: VPC with private/public subnets
- Kubernetes: Amazon EKS
- Databases: RDS MySQL and DocumentDB
- Storage: S3 buckets
- Networking: VPC with private/public subnets
The system supports multiple MongoDB providers with automatic detection:
digitalocean_api
: DigitalOcean API-based database managementatlas
: MongoDB Atlas API-based database management
Provider is automatically detected based on your configuration.
DigitalOcean-managed MongoDB:
export PHD_MONGODB_HOST="mongodb.example.com"
export PHD_MONGODB_PORT="27017"
export PHD_MONGODB_ADMIN_USER="admin"
export PHD_MONGODB_ADMIN_PASSWORD="secure_password"
export PHD_MONGODB_CLUSTER_ID="abc12345-xyz67890"
export PHD_DIGITALOCEAN_TOKEN="dop_v1_your_token"
MongoDB Atlas via CLI:
export PHD_ATLAS_PUBLIC_KEY="your_public_key"
export PHD_ATLAS_PRIVATE_KEY="your_private_key"
export PHD_ATLAS_PROJECT_ID="your_project_id"
export PHD_ATLAS_CLUSTER_NAME="Cluster0"
Note: Atlas provisioning uses the MongoDB Atlas CLI for user management. The CLI is installed automatically from the official MongoDB package repository. Databases are created automatically on first write by your application.
The system automatically computes storage endpoint URLs based on provider type:
- DigitalOcean Spaces: Automatic endpoint formatting (
https://{region}.digitaloceanspaces.com
) - AWS S3: Uses AWS default endpoints
Configuration:
export PHD_STORAGE_TYPE="spaces" # or "s3"
export PHD_STORAGE_REGION="nyc3" # or "us-east-1"
export PHD_STORAGE_ACCESS_KEY_ID="your_key"
export PHD_STORAGE_SECRET_ACCESS_KEY="your_secret"
The repository includes automated GitHub Actions workflows for instance lifecycle management using the Python CLI.
Triggers: Manual workflow dispatch
Inputs:
instance_name
: Unique DNS-compliant instance identifiertemplate_repository
: Repository containing the instance templateplatform_name
: Display name for the Open edX platformedx_platform_repository
: Custom edX Platform fork (optional)edx_platform_version
: Branch/tag to deploy (optional)tutor_version
: Tutor version to use (optional)
What it does:
- Installs PHD CLI using
uv tool install
- Configures kubectl with cluster credentials
- Creates namespace with RBAC policies
- Generates instance configuration using cookiecutter
- Applies provision workflows (MySQL, MongoDB, Storage)
- Creates ArgoCD Application for GitOps deployment
- Waits for provision workflows to complete
Usage:
- Go to Actions → Create Instance
- Click "Run workflow"
- Fill in the required parameters
- Monitor the workflow execution and logs
Triggers: Manual workflow dispatch
Inputs:
instance_name
: Name of the instance to deleteconfirm_deletion
: Must match instance name to proceed (safety check)
What it does:
- Validates deletion confirmation
- Installs PHD CLI using
uv tool install
- Configures kubectl with cluster credentials
- Deletes ArgoCD Application
- Triggers deprovision workflows (MySQL, MongoDB, Storage)
- Waits for deprovision workflows to complete
- Cleans up Kubernetes resources (namespace, RBAC, secrets)
- Removes all instance artifacts
Usage:
- Go to Actions → Delete Instance
- Click "Run workflow"
- Enter instance name and confirm by typing it again
- Monitor the workflow execution and logs
The infrastructure uses Terraform/OpenTofu with S3-compatible backends for state storage. To avoid storing sensitive credentials in the state files, backend credentials must be provided via environment variables.
For AWS S3 backends, set these environment variables:
export AWS_ACCESS_KEY_ID="your-aws-access-key"
export AWS_SECRET_ACCESS_KEY="your-aws-secret-key"
For DigitalOcean Spaces backends, set these environment variables:
export AWS_ACCESS_KEY_ID="your-spaces-access-key"
export AWS_SECRET_ACCESS_KEY="your-spaces-secret-key"
You can also create a separate backend configuration file (e.g., backend.hcl
) that's not committed to version control:
# backend.hcl (add to .gitignore)
bucket = "tfstate-phd-your-cluster-cluster-production"
key = "terraform.tfstate"
access_key = "your-access-key"
secret_key = "your-secret-key"
region = "your-region"
Then initialize with:
tofu init -backend-config=backend.hcl
For GitHub Actions workflows to function properly, configure the following secrets in your repository settings:
Kubernetes Access:
The PHD CLI automatically detects and configures kubeconfig from multiple sources:
- Terraform/OpenTofu Output (Recommended): If you're in a directory with Terraform/OpenTofu that has a
kubeconfig
output, it will be used automatically - Environment Variable: Set
KUBECONFIG_CONTENT
(base64-encoded or plain text) - Existing Config: Falls back to
~/.kube/config
if present
For GitHub Actions, configure:
KUBECONFIG_CONTENT
: Base64-encoded kubeconfig file for cluster access# Generate the secret value: cat ~/.kube/config | base64 -w 0 # Linux cat ~/.kube/config | base64 # macOS
PHD_CLUSTER_DOMAIN
: Your cluster's domain name (e.g.,prod.example.com
)
Note: The PHD CLI will automatically handle kubeconfig setup, so no need to manually configure kubectl in most cases.
MySQL Database (if using MySQL):
PHD_MYSQL_HOST
: MySQL server hostnamePHD_MYSQL_PORT
: MySQL server port (default:3306
)PHD_MYSQL_ADMIN_USER
: MySQL admin usernamePHD_MYSQL_ADMIN_PASSWORD
: MySQL admin password
MongoDB Database (if using MongoDB):
PHD_MONGODB_HOST
: MongoDB server hostnamePHD_MONGODB_PORT
: MongoDB server port (default:27017
)PHD_MONGODB_ADMIN_USER
: MongoDB admin usernamePHD_MONGODB_ADMIN_PASSWORD
: MongoDB admin passwordPHD_MONGODB_CLUSTER_ID
: (Optional) DigitalOcean MongoDB cluster ID for API-based managementPHD_DIGITALOCEAN_TOKEN
: (Optional) DigitalOcean API token if using DO managed databases
Storage (S3/Spaces):
PHD_STORAGE_TYPE
: Storage type (s3
orspaces
)PHD_STORAGE_REGION
: Storage region (e.g.,us-east-1
ornyc3
)PHD_STORAGE_ACCESS_KEY_ID
: Storage access key IDPHD_STORAGE_SECRET_ACCESS_KEY
: Storage secret access key
Terraform Backend (Required for state storage):
AWS_ACCESS_KEY_ID
: Backend storage access key (same asPHD_STORAGE_ACCESS_KEY_ID
)AWS_SECRET_ACCESS_KEY
: Backend storage secret key (same asPHD_STORAGE_SECRET_ACCESS_KEY
)
Note: The backend credentials use the same values as your storage credentials since both use S3-compatible APIs.
- Terraform apply fails: Check cloud provider credentials
- kubectl not working: Verify kubeconfig is set correctly
- GitHub Actions fail: Check that KUBECONFIG_CONTENT secret is set
- ArgoCD not accessible: Check ingress configuration and DNS
- Check GitHub Actions logs
- Verify terraform outputs
- Check ArgoCD and Argo Workflows status
- Review RBAC permissions
The PHD CLI is written in Python and uses uv
for dependency management:
# Clone the repository
git clone https://github.com/open-craft/phd-cluster-template.git
cd phd-cluster-template/tooling
# Install uv (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Sync all dependencies (including dev group)
uv sync --all-groups
# Run tests
uv run pytest
# Run linter
uv run pylint phd/cli/*.py
# Format code
uv run black phd/
uv run isort phd/
# Run all tests
cd tooling
uv run pytest
# Run specific test file
uv run pytest tests/test_password.py
# Run with coverage
uv run pytest --cov=phd --cov-report=term --cov-report=xml
The project uses GitHub Actions for automated testing:
Test Workflow (.github/workflows/test-tooling.yml
):
- Triggers on push/PR to
main
ordevelop
branches - Runs on Python 3.12
- Executes full test suite with coverage reporting
- Runs pylint for code quality checks
- Uploads coverage reports to Codecov
The workflow automatically runs when changes are made to the tooling/
directory.
The project maintains high code quality standards:
- Linting: Pylint with custom rules (
.pylintrc
) - Formatting: Black and isort
- Type Hints: Full type annotations
- Score: 10.00/10 pylint score
# Check code quality
uv run pylint phd/
uv run black --check phd/
uv run isort --check phd/
- Fork the repository
- Create a feature branch (
git checkout -b your-name/new-feature
) - Make your changes
- Run tests and linters:
uv run pytest && uv run pylint phd/
- Format code:
uv run black phd/ && uv run isort phd/
- Commit your changes (
git commit -m 'Add new feature'
) - Push to the branch (
git push origin your-name/new-feature
) - Open a Pull Request
This project is licensed under the AGPL-v3.0 License - see the LICENSE file for details.