cd /news/ai-tools/massdriver-platform-update-november-… · home › topics › ai-tools › article
[ARTICLE · art-159952] src=massdriver.cloud ↗ pub= topic=ai-tools verified=true sentiment=↑ positive

Massdriver Platform Update: November 2026

Massdriver's November 2026 platform release introduces runtimes, letting a platform team write the infrastructure-as-code for how applications run once while developers ship each application as a single massdriver.yaml file, with `mass bundle build`, `lint` and `publish` resolving the newest runtime in a declared version range and merging the application onto it. The release also adds OIDC cloud credentials for deployments, custom default tags, proposal review in the config form, cloud resource import from Claude Code and opencode, search and sort for SCIM-synced members and groups, inspectable and deletable CI service accounts, projects copied from an environment, and config forms that collapse long lists and validate input before deploy. The Platform Engineering Podcast also moved to massdriver.cloud/podcast, where all 52 episodes now live with show notes and full transcripts.

by read16 min views3 publishedOct 28, 2026
Massdriver Platform Update: November 2026
Image: Massdriver (auto-discovered)

Upcoming Workshop: Citizen Developer Agentic Software Factory on AWS 11.4.26 @ 12PM PST

Register Here!

The Platform Engineering Podcast moves to massdriver.cloud, applications that run on runtimes your platform team owns, OIDC cloud credentials for deployments, custom default tags, proposal review in the config form, cloud resource import from Claude Code and opencode, search and sort for members and groups, service accounts you can inspect and delete, projects copied from an environment, and config forms that collapse long lists and check your input before you deploy.

The Platform Engineering Podcast has a new home at massdriver.cloud/podcast, with new artwork. All 52 episodes are there, with show notes and full transcripts. Links to platformengineeringpod.com open the same episode on the new site, and your podcast app subscription keeps working.

New episodes air soon. If you have an idea for a show or a guest, email cory@massdriver.cloud.

If you like the show, please rate it on Apple Podcasts and Spotify.

October made seats follow your identity provider. This release adds runtimes: your platform team writes the IaC for how applications run once, and developers ship an application as one massdriver.yaml. It also makes large organizations easier to manage. You can search the people and groups that SCIM syncs in, see who belongs to what, and inspect and delete the CI service accounts you no longer use.

Write how applications run once. Ship each application as one file.

A runtime is a bundle that defines how applications run on a cloud, for example a Lambda function, a Kubernetes deployment, or an ECS service. Its IaC decides scheduling, networking, IAM, and logging. Developers cannot change it.

An application is a bundle that runs on a runtime. It has no IaC of its own. It adds only its name and version, the dependencies it needs, the environment variables and secrets it reads, and the resources it emits.

A runtime declares provides: [runtime] and an app block that its IaC reads:

name: lambda
version: 1.2.7
provides: [runtime]

app:
  envs:
    LOG_LEVEL: .params.log_level

An application names the runtime with a version range:

name: billing-api
version: 0.1.0

dependencies:
  db:
    resource_type: postgres@~1
    required: true

resources:
  billing:
    resource_type: billing-api@1.0.0
    required: true

steps:
  - path: src
    resources:
      billing:
        template: |
          { url: .outputs.function_url, region: .params.region }

app:
  runtime: lambda@~1.2
  envs:
    DB_HOST: .dependencies.db.host
  secrets:
    STRIPE_KEY:
      required: true

mass bundle build, lint, and publish resolve the newest runtime in the range, pull it, and merge the application onto it. The result publishes as an ordinary bundle, so it deploys, versions, and upgrades the same as every other bundle.

$ mass bundle publish --development
Using runtime lambda@1.2.7 (~1.2, sha256:...)
...
 ✓ Provides check passed.

How the merge works:

steps, params, ui, its own resources, and the IaC. dependencies, app.envs, and app.secrets to the runtime's. If a name is used in both, the build fails.template. Massdriver renders the template after that step applies. lambda@~1.2 is published as lambda@1.2.7, so a later runtime release does not change a published application. Publish the application again to move it to the newer runtime. Publish checks both sides:

provides accepts only runtime today, and each value onceapp blockapp.runtime

lambda@1.2.7 does not provide a runtime. Add `runtime` to `provides` in its massdriver.yaml and republish.

On the API, Bundle.provides returns [RUNTIME] for a runtime. Runtimes need CLI 2.5.0 or later. Runtimes and applications in the docs covers the full merge rules.

Deployments can sign in to your cloud with short-lived OIDC tokens.

Massdriver is now an OIDC identity provider for deployments. Each deployment gets four signed tokens, one for each level: organization, project, environment, and instance. Your cloud trusts Massdriver's public keys and gives a role to the tokens that match your rules. The provisioner gets cloud credentials for the deployment, and you do not store a long-lived key in Massdriver.

Each level's subject adds to its parent's subject:

org:acme
org:acme:project:ecomm
org:acme:project:ecomm:env:prod
org:acme:project:ecomm:env:prod:instance:db

You pick the level in your trust rule. A project-level rule lets developers fork environments and make preview environments with no new cloud credential. An instance-level rule gives a role to one instance only.

Every token has aud set to massdriver:<organization>. Tokens also carry level, organization, project, environment, instance, and deployment_id, so platforms that match on any claim (GCP, Vault, Kubernetes) can use them.

The provisioner step gets each token as a file:

Environment variable File
MASSDRIVER_OIDC_ORGANIZATION_TOKEN_FILE /massdriver/oidc/organization.jwt
MASSDRIVER_OIDC_PROJECT_TOKEN_FILE /massdriver/oidc/project.jwt
MASSDRIVER_OIDC_ENVIRONMENT_TOKEN_FILE /massdriver/oidc/environment.jwt
MASSDRIVER_OIDC_INSTANCE_TOKEN_FILE /massdriver/oidc/instance.jwt

For example, an AWS role that every deployment in the ecomm project can assume:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/api.massdriver.example.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "api.massdriver.example.com:aud": "massdriver:acme",
          "api.massdriver.example.com:sub": "org:acme:project:ecomm"
        }
      }
    }
  ]
}

The bundle's AWS provider reads the project token:

provider "aws" {
  region = "us-east-1"

  assume_role_with_web_identity {
    role_arn                = "arn:aws:iam::123456789012:role/ecomm-deployer"
    web_identity_token_file = "/massdriver/oidc/project.jwt"
  }
}

Self-hosted installs turn this on in the Helm chart with provisioner.identityTokens.signingKey, an RSA private key in PEM format, or with existingSecret. With no key, no tokens are issued and nothing changes.

Value Default Purpose
existingSecret "" A Secret you manage, with a signingKey key and an optionalpreviousPublicKeys key
signingKey "" RSA private key (PEM). When existingSecret is empty, the chart creates the Secret.
previousPublicKeys "" Public keys to keep in the JWKS while you rotate keys
issuer "" Token issuer. Defaults to the API URL. Set it when the discovery documents are on another host.
ttlMinutes 120 Token lifetime in minutes

Discovery is at /.well-known/openid-configuration and /.well-known/jwks.json.

Subjects start with the organization identifier, so a deleted organization's identifier is now retired. No new organization can take it and match the old organization's trust rules.

Choose the attributes that Massdriver tags your cloud resources with.

Every deployment gets a set of default tags in md_metadata.default_tags, and bundles apply them to the cloud resources they create. Until now, every organization got the same tags: managed-by, md-project, md-target, md-manifest, and md-package. Now your organization picks which attributes go in that set. Use system attributes such as md-project and md-environment, or your own custom attributes such as cost_center.

Admins choose the tags on the new Default tags tab in organization settings. managed-by and md-instance are always sent. Each other attribute has a switch. When you create a project, environment, or component attribute, you can select Send as a default tag.

You can also set the list with the API:

mutation {
  updateOrganizationSettings(
    organizationId: "acme"
    input: {
      defaultTags: [
        "md-project"
        "md-environment"
        "md-component"
        "md-instance"
        "cost_center"
      ]
    }
  ) {
    result {
      id
    }
  }
}

The value of each tag is the attribute's value on the instance, merged from the project, the environment, and the instance. An attribute with no value on an instance is left out of that instance's tags. Two tags are always sent:

Tag Value
managed-by massdriver
md-instance The instance identifier, for example api-prod-database

The effectiveAttributes query lists the system and custom attributes that apply to a scope, such as INSTANCE, so you can see what you can select.

Some rules:

_, and -. No organization's tags change until an admin sets defaultTags. The Default tags docs have the full rules.

Costs and alarms follow md-instance. Massdriver now finds the instance for cost data and alarms by the md-instance tag, and uses the old md-package tag when md-instance is not there. This covers AWS Cost and Usage Reports, Azure cost exports, CloudWatch, Google Cloud Monitoring, Azure Monitor, and Prometheus Alertmanager. Instances keep their cost history while their resources move from one tag to the other.

Before you switch to custom default tags, read what each integration needs:

md-instance as a cost allocation tag in the payer account before you switch.md-package must filter on Import existing cloud resources with /massdriver:import.

The Massdriver Claude Code plugin has a new command for cloud resources that already exist: created by hand, by another IaC tool, or in another account.

/massdriver:import existing production RDS Postgres instance created by hand

The command asks how you want to import before it starts:

Paths 1 and 2 put the resource under Massdriver's IaC. The agent runs tofu import against the instance's Massdriver state backend. It does not use import {} blocks, because those put one resource ID into source that every instance shares. Plans run in the Massdriver provisioner, not on your machine. The agent imports, publishes, and plans again until the plan shows no changes. Then it proposes a deployment with those params. Nothing deploys until a person approves the proposal.

Massdriver for opencode. The same skill, agents, and commands now run in opencode. Add the plugin to opencode.json:

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["@massdriver/opencode"]
}

The plugin registers the Massdriver MCP server and adds /massdriver:develop, /massdriver:gen, /massdriver:test-upgrade, /massdriver:import, and /massdriver:architect. It has the same safety guard as the Claude Code plugin:

--development`` update_instance, secrets, environment defaults, and proposals An environment is production when its ID matches (prod|production). You can change the pattern in .opencode/massdriver.local.md. Setup, credentials, and options are in the opencode plugin README.

Search, sort, and page through members and groups on the server.

The Members and Groups tables in organization settings used to show 20 rows with no search. One customer had to go to page 37 to find their Org Admins group.

Both tables now have:

Search, sort, and page size are kept in the URL, so a reload or a shared link opens the same view.

See a member's groups. Click a member's email to open their detail view. It shows their source, whether they hold a seat, and every group they belong to in the organization. Admins who can manage groups can remove the member from a group on this page.

See your own groups. The Organizations tab on your account page lists the groups you belong to in every organization. Click one or more organization cards to filter the table. Pending invitations show in a bar at the top of the tab.

On the API:

Organization.members takes filter: { search } and sort: { field: EMAIL | NAME, order }`` Organization.member(id) returns one membergroups takes filter: { search, accountId, serviceAccountId }`` viewer.groups lists your groups across organizations, with an organizationId filterServiceAccount.lastUsedAt shows when a service account last authenticated a request, and service accounts sort by LAST_USED_AT

query {
  organization(organizationId: "acme") {
    members(filter: { search: "ada" }, sort: { field: NAME, order: ASC }) {
      items {
        email
        holdsSeat
        source
      }
    }
  }
}

Starter groups for new organizations. New organizations get six groups with policies already set: Organization Viewers, Developers, Production Deployers, Platform Engineers, CI / Automation, and Billing. They have no members, and you can rename them, delete them, or change their policies. Developers can propose, and Production Deployers approve in environments where md-environment is prod or production. The access control docs list each group's policies.

The built-in organization viewer group is retired. It had no policies, so its members could do nothing. Each existing viewer group is now a custom group with the same name and members, and it has an allow policy for project:view, repo:view, and resource:view. OIDC autojoin now adds new users to the admin group, and it does not change the groups of users who are already members. ORGANIZATION_VIEWER is removed from the GroupRole and ViewerGroupType enums.

See what a service account can do, and when it was last used.

Click a service account's name to open its detail page. It shows:

You can edit and delete the service account on the same page. The Service accounts table has a sortable Last used column. A warning icon marks accounts that have not been used in more than 90 days, so you can find and delete old CI credentials.

A service account that has deployed can now be deleted.

Before this release, deleting a service account that had ever run a deployment failed with a server error. A service account exists to deploy from CI, so almost every service account in use could not be deleted.

When you delete a service account now:

Record Result
Deployments Kept. The deployment still names the deployer.
Audit logs Kept.
Pending environment deployments Stopped and deleted.
Access tokens Deleted.
Group memberships Deleted.

Deleting a compromised service account stops the environment rollouts it started. A deployment that is already running finishes, because it uses its own deployment token.

Each deployment now stores the name of the account or service account that started it, so deployedBy shows a name after the deployer is deleted. In the audit log, a deleted actor shows as service_account:<id> or account:<id>. Paste that value into the actor filter to find everything else it did.

The disable and reactivate actions for service accounts are removed. No API field used them.

Manage service accounts with Terraform. Terraform provider 2.4.0 adds massdriver_service_account and massdriver_service_account_group_membership. The token is a sensitive attribute that is set only when the service account is created. To rotate it, replace the resource:

resource "time_rotating" "ci_token" {
  rotation_days = 90
}

resource "massdriver_service_account" "ci" {
  name                     = "ci-deployer"
  token_expiration_minutes = 60 * 24 * 100

  lifecycle {
    replace_triggered_by = [time_rotating.ci_token]
  }
}

resource "massdriver_service_account_group_membership" "ci" {
  group_id           = massdriver_group.ci.id
  service_account_id = massdriver_service_account.ci.id
}

Configure one environment, then copy it into as many new projects as you need.

createProjectFromEnvironment creates a new project with the source project's blueprint and one environment. Each new instance uses the same bundle version and release strategy as the source instance. Use it to give each developer their own project, or to start a new service from an environment that already works.

cloneProject copies only the blueprint, so every instance in the new project started empty. copyParams controls which params the new instances get:

NONE`` CONFIGURED``LATEST

mutation {
  createProjectFromEnvironment(
    organizationId: "acme"
    sourceEnvironmentId: "template-dev"
    input: {
      id: "payments"
      name: "Payments"
      environment: { id: "dev", name: "Development" }
      copyParams: LATEST
    }
  ) {
    successful
    result {
      id
    }
  }
}

Fields marked $md.copyable: false are never copied, the same as fork and copy. Secrets, remote references, and environment defaults are not copied. If any step fails, nothing is created.

You need project:view on the source project, project:create for the new project's attributes, and environment:create for the new environment.

In the UI, the environment header has Copy to New Project. It opens a form with the new project's name, attributes, and environment.

Clone Project on the project page now asks what to copy: the full project, or one environment.

Long lists collapse. Every array in a config form shows each item as a header you can collapse. Each array has Expand all and Collapse all. A collapsed item with a validation error shows an error icon in its header, so you can find the item to fix without opening each one.

The header uses the item's name, title, label, or id, in that order. Bundle authors can set their own header with itemTitle in the UI schema:

ui:
  subnets:
    ui:options:
      itemTitle: '{{cidr}} ({{availability_zone}})'

With that setting, each subnet header reads 10.0.0.0/24 (us-east-1a).

Input is checked before you deploy. Deploy and Propose on the Config tab now run the form's checks first. An empty required field or a value that breaks a pattern shows on the field, and nothing is sent. If the server rejects a param, the error shows under that field instead of in a message at the bottom of the screen.

Review proposals in the config form. When an instance has proposals and you have instance:deploy, the Config tab shows how many are waiting. Review opens a picker. The proposal you pick shows in the read-only config form, or as JSON, with Plan, Reject, and Approve. Before this, you could read a proposal only as a table of params in the History tab.

A proposal runs at the bundle version it was proposed on. When that version is not the instance's version, the review shows both, for example v1.2.0 → v1.3.0, and the form uses the fields of the proposed version.

Bundle type has paramsSchema and uiSchema. Before this, you could get a bundle's schemas only through an instance on that exact version. $md.enum options are filled in only on Instance.paramsSchema, because they need an instance.

query {
  bundle(organizationId: "acme", id: "aws-rds-postgres@1.4.0") {
    paramsSchema
    uiSchema
  }
}

massdriver.yaml can set an RJSF UI schema at ui.schema. The import dialog and the resource Settings tab use it to lay out the form, the same way bundle forms use

ui:
  schema:
    ui:order: [project_id, client_email, private_key, '*']
    private_key:
      ui:widget: textarea
      ui:options:
        rows: 8

On the API, ResourceType.uiSchema returns it, or {} when it is not set.

exports can set fileFormat to any lowercase letters and digits, not only yaml. The UI uses the value as the file extension of the download. For example, a VM resource type can export a shell script that connects to the VM:

exports:
  - downloadButtonText: SSH script
    fileFormat: sh
    templatePath: ./exports/connect.sh.liquid
    templateLang: liquid
bash
#!/bin/sh
ssh {{artifact.payload.user}}@{{artifact.payload.host}}

$md.list is a dropdown of the groups, service accounts, or projects in your organization. The saved value is the ID. Each user sees only the options they can view. A value saved by someone else that you cannot view stays in the list as "You cannot view this project", and you can keep it, remove it, or replace it. A save fails if it adds a value that you cannot view.

properties:
  on_call_teams:
    title: On-call Teams
    description: Groups paged for this database's alarms
    type: array
    items:
      type: string
      $md.list:
        resource: group

The Massdriver annotations docs cover $md.list and its publish checks.

GET /v1/resources with a deployment's token (MASSDRIVER_TOKEN in the provisioner) returns every resource the deployment's instance produced. It does not return resources from other instances or imported resources. Other callers get a 403.

[
  {
    "id": "pep-dev-index.table",
    "name": "Table clips",
    "field": "table",
    "resource_type": "document-table@0.0.0",
    "version_constraint": null,
    "available_upgrade": null,
    "payload": {
      "name": "pep-dev-index-8dwd-clips",
      "partition_key": "id",
      "region": "us-east-1"
    }
  }
]

massdriver.sessionTtlHours (default 720, 30 days).

Component Version
Helm chart 0.3.0
Massdriver 2.6.0
UI 2.1.7
CLI 2.5.0
Terraform provider 2.4.0

CLI 2.5.0 adds runtimes and applications to mass bundle build, lint, and publish. Terraform provider 2.4.0 adds service accounts and service account group memberships.

Self-hosted installs pick up the platform and UI images through the chart. Chart 0.3.0 adds the provisioner.identityTokens and massdriver.sessionTtlHours values.

Seats follow your identity provider, custom attributes are safe to change, config can be loaded and promoted from any instance, and external resources show on the environment graph.

Resource types become versioned OCI artifacts, connections carry version ranges, and deployment approvals get separation of duty.

Version 1.3.0 of the Massdriver Platform featuring SCIM 2.0, Integrations Console, and Claude Code Plugin.

── more in #ai-tools 4 stories · sorted by recency
── more on @massdriver 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/massdriver-platform-…] indexed:0 read:16min 2026-10-28 · —