rraform. These scripts create the user identity, assign baseline roles, and provision a sandbox.
2. Environment Definition: Commit a devcontainer.json that includes all necessary CLIs (kubectl, terraform, aws-cli, helm) and pre-configured extensions.
3. Validation Pipeline: Create a CI pipeline that runs when a new user is provisioned. This pipeline verifies access to the sandbox, tests CLI tooling, and ensures the engineer can deploy a "Hello World" resource.
4. First Contribution Workflow: Configure a repository specifically for onboarding tasks. The new hire must complete a safe, non-production task (e.g., updating documentation or adding a tag to a sandbox resource) to validate their workflow.
Code Implementation: Pulumi Onboarding Module
The following TypeScript code demonstrates a Pulumi module that provisions a new engineer's identity and sandbox environment. This script is intended to be run via a CI/CD pipeline triggered by a merge to the onboarding-configs repository.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as github from "@pulumi/github";
// Configuration inputs from the onboarding PR
const config = new pulumi.Config();
const engineerName = config.require("engineerName");
const engineerEmail = config.require("engineerEmail");
const teamId = config.require("teamId");
// 1. Create AWS IAM User with MFA enforcement
const iamUser = new aws.iam.User(`${engineerName}-devops`, {
name: engineerName,
tags: {
Role: "DevOps Engineer",
OnboardingDate: new Date().toISOString(),
},
});
// Attach a restrictive baseline policy
const baselinePolicyAttachment = new aws.iam.UserPolicyAttachment(
`${engineerName}-baseline`,
{
user: iamUser.name,
policyArn: "arn:aws:iam::aws:policy/PowerUserAccess", // Example; use custom baseline in prod
}
);
// Enforce MFA via inline policy
const mfaPolicy = new aws.iam.UserPolicy(`${engineerName}-mfa-enforce`, {
user: iamUser.name,
policy: pulumi.all([iamUser.arn]).apply(([arn]) =>
JSON.stringify({
Version: "2012-10-17",
Statement: [
{
Sid: "AllowAllUsersToListAccounts",
Effect: "Allow",
Action: ["iam:ListAccountAliases", "iam:ListUsers", "iam:GenerateCredentialReport"],
Resource: "*",
},
{
Sid: "AllowIndividualUserToSeeTheirAccountInformation",
Effect: "Allow",
Action: ["iam:GetAccountSummary", "iam:GetUser"],
Resource: arn,
},
{
Sid: "AllowIndividualUserToManageTheirOwnCredentials",
Effect: "Allow",
Action: [
"iam:CreateAccessKey", "iam:CreateLoginProfile", "iam:GetLoginProfile",
"iam:ListAccessKeys", "iam:ListSSHPublicKeys", "iam:ListSigningCertificates",
"iam:UpdateAccessKey", "iam:DeleteAccessKey", "iam:DeactivateMFADevice",
"iam:EnableMFADevice", "iam:ResyncMFADevice",
],
Resource: arn,
},
{
Sid: "BlockAccessIfMFAIsNotPresent",
Effect: "Deny",
NotAction: [
"iam:CreateVirtualMFADevice", "iam:EnableMFADevice", "iam:GetUser",
"iam:ListMFADevices", "iam:ListVirtualMFADevices", "iam:ResyncMFADevice",
"sts:GetSessionToken", "iam:ListAccountAliases",
],
Resource: "*",
Condition: {
Bool: { "aws:MultiFactorAuthPresent": "false" },
},
},
],
})
),
});
// 2. Provision Sandbox S3 Bucket for experimentation
const sandboxBucket = new aws.s3.BucketV2(`${engineerName}-sandbox`, {
bucket: `${engineerName}-sandbox-${config.require("envSuffix")}`,
acl: "private",
tags: {
Project: "Onboarding",
Owner: engineerName,
},
});
// 3. Grant GitHub Repository Access
const repoCollaborator = new github.RepositoryCollaborator(
`${engineerName}-infra-repo-access`,
{
repository: "infrastructure-core",
username: engineerName,
permission: "push",
}
);
// 4. Export outputs for verification pipeline
export const userName = iamUser.name;
export const sandboxBucketName = sandboxBucket.bucket;
export const githubAccessGranted = repoCollaborator.id;
Rationale:
- Idempotency: Pulumi ensures that running the script multiple times results in the same state, preventing duplicate resource errors.
- Security: The IAM policy explicitly denies actions unless MFA is present. This enforces security compliance from day one.
- Traceability: Tags include onboarding dates and ownership, facilitating cost allocation and cleanup.
- Integration: GitHub access is managed via code, ensuring that repository permissions are version-controlled.
Pitfall Guide
-
Hardcoding Credentials in Onboarding Scripts
- Mistake: Embedding API keys or passwords in scripts or READMEs for new hires to use.
- Consequence: Credentials leak into version control history. Even if rotated, the history remains compromised.
- Best Practice: Use dynamic secret generation. Provision users with temporary credentials or OIDC federation. Secrets must be retrieved via a vault at runtime.
-
Granting Full Admin Access Immediately
- Mistake: Providing
AdministratorAccess or root access to speed up the new hire's setup.
- Consequence: Increases blast radius of errors. Violates least privilege principles. Makes auditing difficult.
- Best Practice: Start with a restricted baseline role. Implement a "break-glass" procedure for elevated access that requires justification and automatic expiration.
-
Skipping Environment Parity Verification
- Mistake: Assuming the local setup works because the tools installed, without verifying connectivity to internal services.
- Consequence: New hires discover connectivity issues only when attempting their first deployment, causing delays.
- Best Practice: Include a
make verify target in the devcontainer that checks connectivity to the cluster, registry, and artifact store. Fail fast if verification fails.
-
Manual Approval Bottlenecks for Cloud Access
- Mistake: Requiring manual ticket submission and human approval for every new user's cloud access.
- Consequence: Delays onboarding by days. Creates dependency on specific individuals.
- Best Practice: Automate access via GitOps. Merges to the access config repo trigger automated provisioning. Use policy-as-code (e.g., OPA) to validate requests automatically.
-
Ignoring "Day 1" Runnable Repositories
- Mistake: Documentation that describes steps but lacks executable scripts or templates.
- Consequence: Engineers must manually transcribe commands, introducing typos and configuration drift.
- Best Practice: Every onboarding guide must link to a runnable repository containing Makefiles, scripts, or IaC that performs the setup. Documentation should reference code, not replace it.
-
No Feedback Loop from New Hires
- Mistake: Treating onboarding as a one-way process without measuring the new hire's experience.
- Consequence: Process degradation over time. Friction points are not identified.
- Best Practice: Implement an automated survey or check-in at Day 7 and Day 30. Track TTFM and setup error rates as metrics for the onboarding team.
-
Failing to Test the Onboarding Process Internally
- Mistake: The onboarding team assumes the process works based on their own familiarity.
- Consequence: Blind spots regarding missing dependencies or unclear instructions.
- Best Practice: Schedule quarterly "Dogfooding" sessions where a senior engineer attempts to onboard a fresh environment from scratch using only the documented process. Record failures and fix them.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Startup (<50 engineers) | Lightweight IaC + Shared Sandbox | Speed is critical; overhead of isolated sandboxes may be prohibitive. | Low setup cost; moderate risk. |
| Enterprise (>500 engineers) | Automated GitOps + Ephemeral Sandboxes | Compliance and security require strict isolation and audit trails. | High initial investment; low marginal cost. |
| Regulated Industry (Fin/Health) | Zero-Trust Access + Just-In-Time Privileges | Audit requirements mandate minimal persistent access. | High operational overhead; mitigates compliance risk. |
| Remote-First Team | Cloud-Based Dev Environments | Ensures parity regardless of local hardware or OS. | Increased cloud compute costs; reduced support tickets. |
Configuration Template: devcontainer.json
This template ensures every engineer starts with a consistent, production-aligned environment.
{
"name": "DevOps Standard Environment",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {},
"ghcr.io/devcontainers/features/terraform:1": {},
"ghcr.io/devcontainers/features/aws-cli:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {}
},
"customizations": {
"vscode": {
"extensions": [
"HashiCorp.terraform",
"ms-azuretools.vscode-docker",
"ms-kubernetes-tools.vscode-kubernetes-tools",
"redhat.vscode-yaml",
"github.copilot"
],
"settings": {
"terminal.integrated.defaultProfile.linux": "bash"
}
}
},
"postCreateCommand": "bash .devcontainer/setup.sh",
"remoteEnv": {
"KUBECONFIG": "/workspaces/infrastructure/.kube/config"
}
}
Quick Start Guide
-
Clone the Onboarding Repository:
git clone git@github.com:your-org/devops-onboarding.git
cd devops-onboarding
-
Initialize Environment:
Run the setup script to provision your identity and sandbox. This requires GitHub CLI authentication.
make setup
# Follow prompts to input your name and email.
# Script triggers Pulumi provisioning and configures local kubectl.
-
Verify Access:
Execute the verification target to confirm connectivity and permissions.
make verify
# Checks: AWS STS, K8s API, Helm Registry, GitHub Repo Access.
-
Submit First Contribution:
Edit the docs/team.md file to add your profile and open a PR. This validates your write access and CI pipeline integration.
git checkout -b feat/add-my-profile
# Edit docs/team.md
git commit -m "feat: add engineer profile"
gh pr create --title "Onboarding: Add profile" --body "First contribution."
DevOps onboarding is a force multiplier. By automating the path to production, you reduce risk, accelerate value delivery, and establish a culture of engineering excellence from the moment a new hire joins. Treat onboarding as a product, iterate based on data, and enforce consistency through code.