Machines
Define and discover your deployment target machines for NixOS deployments
Stackpanel's machine inventory system lets you define the servers your applications deploy to. Machines can be declared statically in Nix or discovered dynamically from AWS EC2. The inventory feeds into Colmena for NixOS deployments and is visible in the Studio UI's Deploy panel.
For the canonical deployment/provisioning contract, including stackpanel.deployment.machines, stackpanel deploy, and stackpanel provision, see docs/superpowers/specs/2026-03-28-deployment-system.md in the repository. This page focuses on machine-inventory and infrastructure-facing workflows.
How It Works
Machine inventory flows through three layers:
- Nix config -- You declare machines (static) or EC2 discovery rules in
.stack/config.nix - Infra module -- The machines infra module resolves the inventory at deploy time (for EC2, it queries the AWS API)
- Studio UI -- The Deploy panel reads the resolved inventory and displays machine cards, app-to-machine mappings, and Colmena actions
Quick Start
Enable the machines module
Add to your .stack/config.nix:
stackpanel.infra.machines = {
enable = true;
source = "static"; # or "aws-ec2"
machines = {
web-1 = {
name = "Web Server 1";
host = "10.0.1.10";
roles = [ "web" ];
tags = [ "production" ];
arch = "x86_64-linux";
ssh.user = "deploy";
};
};
};Pull outputs and reload
Pull the resolved inventory into your local state and reload the devshell:
infra:pull-outputs
exit && nix develop --impureView in Studio
Open the Studio UI and navigate to Deploy. Your machines appear in the Machines tab with their status, roles, and connection info.
Static Machines
For fixed infrastructure (bare metal, pre-provisioned VMs, Hetzner, etc.), declare machines directly:
stackpanel.infra.machines = {
enable = true;
source = "static";
machines = {
web-1 = {
name = "Web Server 1";
host = "web1.example.com";
roles = [ "web" "app" ];
tags = [ "production" "us-east" ];
provider = "hetzner";
arch = "x86_64-linux";
publicIp = "203.0.113.10";
privateIp = "10.0.1.10";
targetEnv = "production";
ssh = {
user = "deploy";
port = 22;
keyPath = "~/.ssh/deploy_ed25519";
};
labels = {
team = "platform";
cost-center = "infra";
};
};
db-1 = {
name = "Database Primary";
host = "10.0.2.10";
roles = [ "database" ];
tags = [ "production" ];
arch = "x86_64-linux";
targetEnv = "production";
ssh.user = "root";
};
};
};Machine Properties
| Property | Type | Description |
|---|---|---|
name | string | Human-friendly display name |
host | string | SSH hostname or IP address |
ssh.user | string | SSH user (default: root) |
ssh.port | int | SSH port (default: 22) |
ssh.keyPath | string? | Path to SSH private key |
roles | [string] | Roles for app-to-machine targeting |
tags | [string] | Tags for grouping and filtering |
provider | string? | Infrastructure provider (aws, hetzner, etc.) |
arch | string? | System architecture (e.g., x86_64-linux) |
publicIp | string? | Public IP address |
privateIp | string? | Private IP address |
targetEnv | string? | Environment label (production, staging, etc.) |
labels | {string: string} | Arbitrary key-value metadata |
nixosProfile | string? | NixOS profile to deploy |
nixosModules | [string] | Extra NixOS modules for this machine |
env | {string: string} | Environment variables for this machine |
metadata | {string: any} | Extra metadata for downstream tooling |
AWS EC2 Discovery
For dynamic infrastructure, Stackpanel can discover machines from AWS EC2 at deploy time:
stackpanel.infra.machines = {
enable = true;
source = "aws-ec2";
aws = {
region = "us-west-2";
# EC2 filters (same syntax as AWS CLI --filters)
filters = [
{ name = "instance-state-name"; values = [ "running" ]; }
{ name = "tag:Project"; values = [ "myapp" ]; }
];
};
};When infra:deploy runs, the machines module queries the EC2 API using the specified filters and builds an inventory from the results.
EC2 Tag Mapping
EC2 instances are mapped to machine properties using configurable tag keys:
| Machine Property | Default Tag Keys | Example Tag Value |
|---|---|---|
name | Name | web-server-1 |
roles | stackpanel:role, role | web,app |
tags | stackpanel:tag, tag | production,us-west |
targetEnv | stackpanel:env, env, stage | production |
Customize the tag keys used for mapping:
stackpanel.infra.machines.aws = {
region = "us-west-2";
# Which EC2 tags map to machine roles
role-tag-keys = [ "stackpanel:role" "Role" ];
# Which EC2 tags map to machine tags
tag-keys = [ "stackpanel:tag" "Environment" ];
# Which EC2 tags map to target environment
env-tag-keys = [ "stackpanel:env" "Stage" ];
# Which EC2 tags provide the machine name
name-tag-keys = [ "Name" ];
filters = [
{ name = "instance-state-name"; values = [ "running" ]; }
];
};Host Resolution
When discovering EC2 instances, Stackpanel picks the SSH host using a preference order:
stackpanel.infra.machines.aws = {
# Try public DNS first, then public IP, then private IP
host-preference = [ "publicDns" "publicIp" "privateIp" ];
};For VPN or private-network setups where you connect to private IPs:
stackpanel.infra.machines.aws = {
host-preference = [ "privateIp" ];
};Default SSH Settings for EC2
Apply SSH defaults to all discovered EC2 machines:
stackpanel.infra.machines.aws = {
ssh = {
user = "ec2-user";
port = 22;
keyPath = "~/.ssh/aws_deploy";
};
};Filtering by Instance IDs
Target specific instances instead of using filters:
stackpanel.infra.machines.aws = {
instance-ids = [
"i-0abc123def456789a"
"i-0def456789abc1230"
];
};Provisioning EC2 Instances
Beyond discovering existing machines, Stackpanel can provision new EC2 instances via the aws-ec2-app infra module. This creates instances with auto-resolved AMIs (Ubuntu 24.04 or NixOS), security groups, IAM roles, and key pairs.
From the Studio UI
- Open the Deploy panel and go to the Provision tab
- Click Provision EC2 to open the configuration dialog
- Fill in the instance group: app ID, count, instance type, OS, networking, SSH key, IAM
- Click Add to Config -- this writes to your Nix config
- Run
infra:deployfrom the terminal to create the instances - Run
infra:pull-outputsand reload the shell to see them in the Machines tab
From Nix Config
stackpanel.infra.aws-ec2-app = {
enable = true;
apps = {
web-server = {
instance-count = 2;
instance-type = "t3.small";
os-type = "ubuntu"; # or "nixos"
associate-public-ip = true;
# Auto-creates a security group with SSH + HTTP + HTTPS
security-group.create = true;
# Auto-creates IAM role with SSM + ECR access
iam.enable = true;
# Import your SSH public key
key-pair = {
create = true;
public-key = "ssh-ed25519 AAAA...";
};
# Machine metadata for Colmena targeting
machine = {
roles = [ "web" ];
tags = [ "production" ];
target-env = "production";
};
};
};
};Then deploy:
infra:deploy
infra:pull-outputs
exit && nix develop --impureThe provisioned instances automatically appear in your machine inventory with their public IPs, and can be targeted by Colmena for NixOS deployments.
What Gets Created
For each app group, aws-ec2-app provisions:
| Resource | Created when |
|---|---|
| Security Group | security-group.create = true (default) |
| SSH Key Pair | key-pair.create = true |
| IAM Role + Instance Profile | iam.enable = true (default) |
| EC2 Instances | Always (count from instance-count) |
| ALB + Target Group | alb.enable = true |
| ECR Repository | ecr.enable = true |
| SSM Parameters | ssm.enable = true |
VPC and subnets are auto-discovered from your default VPC unless you specify vpc-id and subnet-ids explicitly.
The aws-ec2-app module auto-resolves AMIs. For Ubuntu, it finds the latest Ubuntu 24.04 LTS from Canonical. For NixOS, it finds the latest Determinate Systems NixOS image. You can override with an explicit ami value.
Studio UI: Deploy Panel
The Studio UI's Deploy panel (/studio/deploy) provides a visual interface for your machine inventory. It has five tabs:
Machines Tab
Displays a card grid of all machines in your inventory. Each card shows:
- Machine name and hostname
- SSH connection info (user, port)
- Architecture and provider
- Roles (colored badges) and tags
- Reachability status (green check or amber warning)
Click a machine card to edit its configuration. Use the Add Machine button in the header to add static machines.
When the inventory is empty, it shows guidance to add machines or configure EC2 discovery.
Provision Tab
Configure EC2 instances to create. Each row shows the instance group with its count, type, OS, and what resources will be created (SG, IAM, Key). Click Provision EC2 to define a new group. After configuring, run infra:deploy to create the actual instances.
App Targets Tab
Shows which apps are configured for deployment and their target machines. Each row displays:
- App name
- Target machines (resolved from roles or explicit targets)
- Role badge (if targeting by role)
- Machine count
Actions Tab
Provides buttons for Colmena deployment actions:
- colmena eval -- Evaluate the NixOS configuration
- colmena build -- Build the NixOS closures
- colmena apply -- Deploy to target machines
The Actions tab is disabled when no machines are in the inventory. Provision infrastructure first with infra:deploy.
Settings Tab
Displays the current Colmena configuration: machine source, hive config path, whether hive generation is enabled, and total machine count.
Mapping Apps to Machines
After machines are in your inventory, configure apps to deploy to them:
stackpanel.apps.api = {
port = 1;
root = "./apps/api";
# Deploy this app to machines with the "web" role
deploy = {
enable = true;
role = "web";
};
};
stackpanel.apps.worker = {
port = 2;
root = "./apps/worker";
# Deploy to specific machines by name
deploy = {
enable = true;
targets = [ "worker-1" "worker-2" ];
};
};End-to-End Example
Here is a complete setup with AWS EC2 discovery, app targeting, and infra provisioning:
{
# Infrastructure provisioning
stackpanel.infra = {
enable = true;
storage-backend = {
type = "sops";
sops.group = "dev";
};
# AWS secrets (IAM role, KMS key)
aws.secrets = {
enable = true;
oidc.provider = "github-actions";
oidc.github-actions = {
org = "my-org";
repo = "my-repo";
};
};
# Machine discovery
machines = {
enable = true;
source = "aws-ec2";
aws = {
region = "us-west-2";
filters = [
{ name = "instance-state-name"; values = [ "running" ]; }
{ name = "tag:Project"; values = [ "myapp" ]; }
];
ssh.user = "deploy";
};
};
};
# Apps with deployment targets
stackpanel.apps = {
web = {
port = 0;
root = "./apps/web";
deploy = {
enable = true;
role = "web";
};
};
api = {
port = 1;
root = "./apps/api";
deploy = {
enable = true;
role = "api";
};
};
};
}Then deploy:
# Provision infrastructure and discover machines
infra:deploy
# Pull machine inventory into local state
infra:pull-outputs
# Reload shell to pick up new inventory
exit && nix develop --impure
# View machines in Studio
# Navigate to /studio/deploy
# Deploy with Colmena
colmena applyDeploy flake outputs
By default, nixosConfigurations and colmenaHive are exposed on the project
flake. For monorepos with heavy internal host configs, set:
stackpanel.deployment.flakeOutputs = {
expose = false;
flakeDir = "./deploy";
};Deploy tooling (stackpanel deploy, stackpanel provision, colmena wrappers)
reads flakeDir for --flake references. See deploy/README.md in the
stackpanel repo for the internal sub-flake layout.
Troubleshooting
"No Machines" in the Deploy panel
The machine inventory is populated by infra:deploy. If you see no machines:
- Run
infra:deployto execute the machines infra module - Run
infra:pull-outputsto sync the inventory to local state - Reload your devshell (
exit && nix develop --impure) - Refresh the Deploy panel in Studio
EC2 discovery returns no instances
Check your filters and region. Test directly with the AWS CLI:
aws ec2 describe-instances \
--region us-west-2 \
--filters "Name=instance-state-name,Values=running" "Name=tag:Project,Values=myapp" \
--query 'Reservations[].Instances[].{Id:InstanceId,Name:Tags[?Key==`Name`].Value|[0]}'SSH connection failures
Verify SSH connectivity to the resolved host:
ssh -o ConnectTimeout=5 deploy@<machine-host> 'echo ok'Common issues:
- Security group doesn't allow SSH from your IP
- SSH key path is incorrect
- Using public DNS but instance only has a private IP (adjust
host-preference)
Reference
- Deployment Overview for all deployment options
- Infrastructure for the infra module system
- Fly.io for container-based deployment
- Cloudflare for edge deployment