TL;DR — Quick Fix
Get a new engineer running code in under 30 minutes with a Devcontainer:
{
"name": "Platform Dev Environment",
"image": "ghcr.io/my-org/devcontainer:latest",
"features": {
"ghcr.io/devcontainers/features/aws-cli:1": {},
"ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {},
"ghcr.io/devcontainers/features/terraform:1": {},
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
},
"postCreateCommand": "./scripts/setup-dev.sh",
"forwardPorts": [3000, 5432, 6379],
"customizations": {
"vscode": {
"extensions": [
"ms-kubernetes-tools.vscode-kubernetes-tools",
"hashicorp.terraform",
"redhat.vscode-yaml"
]
}
}
}
#!/bin/bash
# scripts/setup-dev.sh — runs automatically on container creation
set -euo pipefail
echo "Setting up development environment..."
npm ci
docker compose up -d postgres redis
sleep 5
npm run db:migrate
npm run db:seed
npm run test:smoke
echo "Setup complete. Run 'npm run dev' to start."
---
Architecture — Developer Onboarding Pipeline
---
Step 1 — Standardized Dev Environments
Option A: Devcontainers (Recommended)
{
"name": "Backend Service",
"build": {
"dockerfile": "Dockerfile.dev",
"context": ".."
},
"features": {
"ghcr.io/devcontainers/features/node:1": { "version": "20" },
"ghcr.io/devcontainers/features/aws-cli:1": {},
"ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {},
"ghcr.io/devcontainers/features/terraform:1": { "version": "1.7" },
"ghcr.io/devcontainers/features/github-cli:1": {}
},
"postCreateCommand": "make setup",
"postStartCommand": "make dev-services",
"forwardPorts": [3000, 5432, 6379, 8080],
"remoteEnv": {
"APP_ENV": "development",
"DATABASE_URL": "postgres://dev:dev@localhost:5432/app_dev"
}
}
Option B: Nix Flakes
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let pkgs = nixpkgs.legacyPackages.${system};
in {
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
nodejs_20 go_1_22 python312
terraform kubectl helm awscli2
docker-compose jq yq
];
shellHook = ''
echo "Dev environment ready."
export KUBECONFIG=$PWD/.kube/config
'';
};
});
}
---
Step 2 — Automated Setup Scripts
# Makefile targets for onboarding
.PHONY: setup dev-services seed test-smoke
setup: ## First-time setup for new engineers
@echo "Installing dependencies..."
npm ci
@echo "Copying environment template..."
cp .env.example .env.local
@echo "Starting infrastructure..."
$(MAKE) dev-services
@echo "Running database migrations..."
npm run db:migrate
@echo "Seeding development data..."
npm run db:seed
@echo "Verifying setup..."
$(MAKE) test-smoke
@echo "Setup complete! Run 'make dev' to start."
dev-services: ## Start local infrastructure
docker compose up -d postgres redis localstack
@echo "Waiting for services..."
docker compose exec postgres pg_isready -U dev
seed: ## Seed database with test data
npm run db:seed -- --profile=development
test-smoke: ## Quick smoke test
npm run test:smoke
@echo "All smoke tests passed!"
---
Step 3 — Golden Path Templates
# backstage-template.yaml — New service template
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: microservice-template
title: Create a New Microservice
description: Production-ready microservice with CI/CD and monitoring
spec:
owner: platform-team
type: service
parameters:
- title: Service Details
required: [name, team, language]
properties:
name:
title: Service Name
type: string
pattern: "^[a-z][a-z0-9-]*$"
team:
title: Owning Team
type: string
enum: [payments, orders, platform, data]
language:
title: Language
type: string
enum: [typescript, go, python]
steps:
- id: fetch-template
name: Fetch Template
action: fetch:template
input:
url: ./skeleton/${{ parameters.language }}
values:
name: ${{ parameters.name }}
team: ${{ parameters.team }}
- id: create-repo
name: Create GitHub Repo
action: publish:github
input:
repoUrl: github.com?owner=my-org&repo=${{ parameters.name }}
defaultBranch: main
- id: register-catalog
name: Register in Backstage
action: catalog:register
input:
repoContentsUrl: ${{ steps['create-repo'].output.repoContentsUrl }}
catalogInfoPath: /catalog-info.yaml
---
Step 4 — Access Provisioning Automation
# terraform/modules/engineer-access/main.tf
variable "engineer" {
type = object({
email = string
team = string
role = string
})
}
resource "github_team_membership" "engineer" {
team_id = data.github_team.team[var.engineer.team].id
username = split("@", var.engineer.email)[0]
role = var.engineer.role == "lead" ? "maintainer" : "member"
}
resource "aws_ssoadmin_account_assignment" "engineer" {
instance_arn = data.aws_ssoadmin_instances.main.arns[0]
permission_set_arn = local.permission_sets[var.engineer.role]
principal_id = aws_identitystore_user.engineer.user_id
principal_type = "USER"
target_id = local.account_ids[var.engineer.team]
target_type = "AWS_ACCOUNT"
}
---
Step 5 — Onboarding Checklist
## Day 1 Onboarding Checklist
Automated (completes in < 30 min)
- [ ] GitHub org access granted
- [ ] AWS SSO account created
- [ ] Slack channels joined
- [ ] PagerDuty account created (shadow rotation first)
- [ ] 1Password vault access
- [ ] VPN credentials provisioned
Self-Service (guided, < 2 hours)
- [ ] Clone main repository
- [ ] Open in Devcontainer (or run nix develop)
- [ ] Run make setup — verify all tests pass
- [ ] Complete Hello World tutorial (first PR)
- [ ] Deploy to staging environment
- [ ] Read architecture overview in dev portal
First Week Goals
- [ ] Complete one real ticket (pair with buddy)
- [ ] Shadow on-call rotation
- [ ] Read post-mortem from last month
- [ ] Add yourself to team service ownership
- [ ] Attend team standup and retro
---
Frequently Asked Questions
How long should onboarding take for a senior engineer?
Target is first production deploy within 4 hours, first real feature shipped within the first week. Senior engineers should be fully autonomous by week 2. If onboarding takes longer than a day for basic setup, your developer experience needs improvement.
Devcontainers vs Nix — which should we use?
Devcontainers for teams using VS Code or GitHub Codespaces where the primary language is JavaScript/TypeScript or Python. Nix for multi-language environments or teams with diverse editor preferences. You can combine both — use Nix inside a Devcontainer for maximum reproducibility.
How do we handle secrets in the onboarding flow?
Never put real secrets in setup scripts. Use local-only mock credentials (LocalStack for AWS, Docker Compose for databases), or integrate with a secrets manager that provisions per-developer credentials. Use .env.local files that are gitignored.
Should new engineers get production access on day one?
Read-only production access (logs, metrics, dashboards) on day one is fine and helpful. Write access (deployments, database modifications) should wait until the engineer has merged at least one PR and completed the staging deploy tutorial.
How do we measure onboarding effectiveness?
Track these metrics: (1) Time to first commit, (2) Time to first production deploy, (3) Number of onboarding questions in Slack, (4) New engineer satisfaction survey at 30 days, (5) Time to be added to on-call rotation.
---