Apr 12, 2026 8 min read

Automating VM Templates with Packer, Ansible, and TypeScript

DevOps Automation TypeScript

At BTPN, provisioning a new virtual machine template used to take 4-6 hours. It was manual, error-prone, and required deep knowledge of each target platform. We built epigon CLI to reduce that to 15 minutes.

The Problem

Our infrastructure spans multiple platforms: VMware vSphere, OpenStack, and Azure. Each has its own image format, provisioning API, and configuration quirks. Manually creating templates meant:

  1. SSH into the base image
  2. Run a series of bash scripts for hardening
  3. Install required packages
  4. Apply security policies
  5. Convert to the target platform’s format
  6. Upload to the image registry
  7. Test

For three platforms, this was an all-day affair. Worse, configuration drift between templates was common — the Azure template would have a different OpenSSL version than the VMware one.

Architecture

epigon CLI uses a three-layer architecture:

TypeScript CLI (Commander.js)

Packer Templates (HCL)

Ansible Playbooks (YAML)

Layer 1: TypeScript CLI. The user-facing interface. Handles configuration validation, interactive prompts, and orchestration. Built with Commander.js for argument parsing and a custom plugin system for extensibility.

Layer 2: Packer. HashiCorp Packer handles the heavy lifting of image creation. We define a single source configuration and multiple builder blocks — one per target platform. Packer launches a temporary VM, provisions it, and captures the result as a template.

Layer 3: Ansible. The actual configuration happens through Ansible playbooks. This is where we handle hardening (CIS benchmarks), package installation, security policies, and application-specific setup.

The key insight: Packer runs Ansible in the temporary VM, so all configuration is tested before the template is finalized.

Design Decisions

Why TypeScript? We already had a TypeScript ecosystem. Engineers writing custom plugins shouldn’t need to learn a new language. The type system catches configuration errors at build time rather than runtime.

Why not Terraform? Terraform is excellent for infrastructure, but image creation is a different problem. Packer’s VM-based approach ensures the template is actually bootable — something you can’t guarantee with a pure configuration tool.

Plugin system. Each team has unique requirements for their VMs. Instead of hardcoding every configuration, we built a plugin interface that teams implement:

interface TemplatePlugin {
  name: string;
  ansiblePlaybooks: string[];
  validate(config: Config): ValidationResult;
  postBuild(template: Template): Promise<void>;
}

Lessons Learned

Validation should be the first thing you build. We spent two weeks building a comprehensive validation layer. It paid for itself in the first month — catching misconfigurations before they wasted hours of build time.

Error messages are part of the UX. When a Packer build fails 30 minutes in, the error message better be clear. We invested heavily in error formatting and remediation suggestions.

Cache aggressively. Building templates from scratch is slow. We cache intermediate layers and reuse them when possible. A full rebuild takes 15 minutes; an incremental update takes 3.

Results

  • Build time: 4-6 hours → 15 minutes (95% reduction)
  • Platforms supported: 3 → 5
  • Configuration drift: Eliminated

epigon CLI is now the default way to create VM templates at BTPN. What started as an internal tool has become essential infrastructure.