Workload Placement Examples

These worked examples give you copy-ready configurations once you know what you want. For the concepts, configuration components, and field options, see Workload Placement.

Implementation Examples

Optimizing for Cost and Performance

This example creates a cost-optimized infrastructure with dedicated node pools for different workload types.

  1. Rack Configuration:

    $ convox rack params set additional_node_groups_config=/path/to/node-groups.json -r production
    $ convox rack params set additional_build_groups_config=/path/to/build-groups.json -r production
    

    Node groups config (AWS):

    [
      {
        "id": 101,
        "type": "t3.medium",
        "capacity_type": "ON_DEMAND",
        "min_size": 1,
        "max_size": 5,
        "label": "critical-services",
        "tags": "department=frontend,environment=production,cost-center=web"
      },
      {
        "id": 102,
        "type": "c5.large",
        "capacity_type": "SPOT",
        "min_size": 0,
        "max_size": 10,
        "label": "batch-workers",
        "tags": "department=data,environment=production,cost-center=analytics"
      }
    ]
    

    Node groups config (Azure):

    [
      {
        "id": 101,
        "type": "Standard_D4s_v3",
        "capacity_type": "ON_DEMAND",
        "min_size": 1,
        "max_size": 5,
        "label": "critical-services",
        "tags": "department=frontend,environment=production,cost-center=web"
      },
      {
        "id": 102,
        "type": "Standard_E4s_v3",
        "capacity_type": "SPOT",
        "min_size": 0,
        "max_size": 10,
        "label": "batch-workers",
        "tags": "department=data,environment=production,cost-center=analytics"
      }
    ]
    

    Build groups config (AWS):

    [
      {
        "type": "c5.xlarge",
        "capacity_type": "SPOT",
        "min_size": 0,
        "max_size": 5,
        "label": "app-build"
      }
    ]
    

    Build groups config (Azure):

    [
      {
        "type": "Standard_D8s_v3",
        "capacity_type": "SPOT",
        "min_size": 0,
        "max_size": 5,
        "label": "app-build"
      }
    ]
    
  2. Application Configuration:

    $ convox apps params set BuildLabels=convox.io/label=app-build -a myapp
    $ convox apps params set BuildCpu=1000 BuildMem=4096 -a myapp
    
  3. Service Configuration (in convox.yml):

    services:
      web:
        build: .
        port: 3000
        nodeSelectorLabels:
          convox.io/label: critical-services
      worker:
        build: ./worker
        nodeSelectorLabels:
          convox.io/label: batch-workers
    

This configuration creates:

  • On-demand nodes for critical services like web frontends
  • Spot instance nodes for batch processing workloads
  • Separate spot instance nodes optimized for build processes
  • Provider tags for accurate cost attribution across teams and environments

Isolating High-Priority Workloads

To create dedicated node groups that exclusively run specific services:

  1. Create a JSON file for your node group configuration:

    AWS:

    [
      {
        "id": 103,
        "type": "m5.large",
        "capacity_type": "ON_DEMAND",
        "min_size": 2,
        "max_size": 5,
        "label": "database-workers",
        "dedicated": true,
        "tags": "environment=production,team=datastore,criticality=high"
      }
    ]
    

    Azure:

    [
      {
        "id": 103,
        "type": "Standard_E4s_v3",
        "capacity_type": "ON_DEMAND",
        "min_size": 2,
        "max_size": 5,
        "label": "database-workers",
        "dedicated": true,
        "tags": "environment=production,team=datastore,criticality=high"
      }
    ]
    
  2. Apply the configuration:

    $ convox rack params set additional_node_groups_config=/path/to/dedicated-nodes.json -r production
    
  3. Service Configuration (in convox.yml):

    services:
      db-processor:
        build: ./processor
        nodeSelectorLabels:
          convox.io/label: database-workers
    

With dedicated:true, only services that explicitly select the node group will run on it, ensuring isolation for sensitive workloads.

Mixed ARM/x86 Architecture

This example sets up an x86 primary rack with ARM worker nodes and ARM build nodes, allowing cost-optimized Graviton instances for selected apps while keeping x86 available for compatibility-sensitive workloads.

The BuildArch app parameter takes effect on AWS Racks running 3.25.3 or later. On earlier Racks the CLI accepted the value and the Rack discarded it, so step 2 below had no effect and convox apps params never listed it.

  1. Add ARM worker nodes, ARM build nodes, and dedicated build nodes in one call.

    ARM worker nodes, dedicated so only targeted apps land on them, in arm-nodes.json:

    [
      {
        "id": 104,
        "type": "t4g.medium",
        "capacity_type": "ON_DEMAND",
        "min_size": 1,
        "max_size": 5,
        "label": "arm-workers",
        "dedicated": true,
        "tags": "architecture=arm64,environment=production"
      }
    ]
    

    ARM build nodes, so ARM apps build natively without emulation, in arm-build-nodes.json:

    [
      {
        "id": 201,
        "type": "t4g.medium",
        "capacity_type": "ON_DEMAND",
        "min_size": 1,
        "max_size": 2
      }
    ]
    
    $ convox rack params set additional_node_groups_config=/path/to/arm-nodes.json additional_build_groups_config=/path/to/arm-build-nodes.json build_node_enabled=true -r production
    

    Batch the parameters into one command. A Rack update holds a Terraform state lock, so a second convox rack params set issued before the first finishes is rejected. Wait for convox rack params to return the parameter list before moving on.

    The kubernetes.io/arch node affinity that BuildArch applies is only added when build_node_enabled is true. The default is false, which leaves the build Pod free to schedule on any node.

  2. Configure each ARM app to build on the correct architecture:

    $ convox apps params set BuildArch=arm64 -a myapp
    
  3. Target the app to ARM workers in convox.yml:

    services:
      web:
        build: .
        port: 3000
        nodeSelectorLabels:
          convox.io/label: arm-workers
    
  4. Deploy:

    $ convox deploy -a myapp
    

With build_node_enabled=true, the build runs on an ARM build node (via BuildArch=arm64), producing a native ARM image. Without dedicated build nodes the build Pod is unconstrained, but BuildArch still pins the produced image to arm64, so the build is emulated rather than native. The resulting Pods run on the dedicated ARM worker nodes (via nodeSelectorLabels). Apps without BuildArch continue building and running on x86 nodes as before.

Reverse direction works the same way: if your primary rack uses ARM (e.g., node_type=t4g.medium), add x86 additional groups and set BuildArch=amd64 on apps that need Intel/AMD compatibility.

Key considerations:

  • This walkthrough covers managed node groups. On a Karpenter Rack with karpenter_arch=amd64,arm64, builds produce a multi-architecture image index and no per-App pinning is needed. See Architecture Selection and Mixed-Architecture Racks.
  • BuildArch is per-app, not per-service. Apps with services targeting different architectures should be split into separate apps.
  • Convox system images (including Fluentd) are multi-arch manifests and run natively on both architectures with no configuration.
  • The kubernetes.io/arch label is set automatically by the kubelet, so this works on AWS, Azure, and GCP without provider-specific setup.

For full BuildArch parameter details, see BuildArch.

Flexible Configuration Options

Convox allows you to implement different levels of customization based on your needs:

  1. Build Isolation Only: Configure only additional_build_groups_config to isolate build processes while keeping services on standard nodes:

    $ convox rack params set additional_build_groups_config=/path/to/build-groups.json -r production
    $ convox apps params set BuildLabels=convox.io/label=app-build -a myapp
    
  2. Service Placement Only: Configure only additional_node_groups_config to customize service placement while letting builds run on standard nodes:

    $ convox rack params set additional_node_groups_config=/path/to/node-groups.json -r production
    

    In your convox.yml:

    services:
      web:
        nodeSelectorLabels:
          convox.io/label: critical-services
    
  3. Complete Workload Management: Implement both configurations for full control over placement of both services and build processes.