Production engineering guide for engineering onboarding covering patterns, implementation strategies, and operational best practices.
Engineering onboarding is the structured process of integrating new engineers into an organisation’s engineering culture, tools, and workflows. It begins on day one and extends over the first 90 to 180 days, ensuring that engineers can independently contribute to code, ship features, and participate in cross-team decisions. This process matters most when a team grows rapidly, when onboarding is ad hoc, or when engineers are distributed across time zones and locations.
On day one, the new engineer receives a laptop with a pre-configured environment. The first task is to clone the main repository and run make bootstrap:
git clone [email protected]:acme/eng-platform.git
cd eng-platform
make bootstrap
make bootstrap runs a series of scripts to install dependencies, set up databases, and start services. It relies on a Makefile with defined targets:
.PHONY: bootstrap
bootstrap: install-deps setup-db start-services
install-deps:
pip install -r requirements.txt
npm install
./scripts/install-tools.sh
setup-db:
createdb eng_platform_dev
./scripts/migrate.sh
start-services:
docker-compose up -d api db cache
The scripts/install-tools.sh script installs kubectl, helm, jq, yq, and terraform using apt and brew, with version pins in a tools.lock file:
{
"kubectl": "v1.28.0",
"helm": "v3.11.3",
"jq": "1.7.1",
"yq": "4.40.5",
"terraform": "1.5.7"
}
If make bootstrap fails, the output includes a summary of failed steps and a diagnostic command:
make bootstrap
# ✅ install-deps
# ✖️ setup-db
# Error: Failed to create database: psql: FATAL: database "eng_platform_dev" does not exist
# Run: sudo -u postgres psql -c "CREATE DATABASE eng_platform_dev;"
# ✅ start-services
# 📌 Run: make diagnose-bootstrap to examine logs
The make diagnose-bootstrap command collects logs from all services, database errors, and tool versions:
#!/bin/bash
echo "=== Diagnostic Report ==="
echo "Time: $(date)"
echo "Tools:"
jq -r '.[] | .name + " " + .version' tools.lock
echo "Services:"
docker-compose ps
echo "Database logs:"
docker-compose logs db | tail -n 20
By the end of the first week, the engineer must submit their first pull request. The PR template is a standard PULL_REQUEST_TEMPLATE.md file in the repository root:
## Description
This PR adds a new API endpoint to fetch user preferences.
### Changes
- Adds `GET /api/v1/preferences` endpoint
- Implements caching with Redis
- Adds unit and integration tests
### Related Issues
- #123: User preferences feature
- #456: Add caching layer
### Acceptance Criteria
- [x] Endpoint returns 200 with valid user ID
- [x] Returns empty object when user has no preferences
- [x] Caching layer is hit on subsequent requests
- [x] CI passes on all checks
The CI pipeline runs on every push and is defined in .github/workflows/ci.yml:
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- run: pip install -r requirements.txt
- run: pytest tests/
- name: Upload coverage
uses: actions/upload-artifact@v3
with:
name: coverage
path: coverage.xml
- name: Upload test results
uses: EnricoMi/publish-unit-test-result-action@v2
with:
path_to_reports: test-results.xml
When a PR fails, the GitHub UI shows a summary of the failed test and a link to the test logs. The engineer must understand that a failing pytest step means the test suite failed, but a failing Upload test results step means the test report was generated but not properly uploaded.
The pytest output includes a test-results.xml file, which is consumed by the publish-unit-test-result-action:
<testsuite name="test_preferences" tests="6" failures="1" errors="0" time="1.234">
<testcase classname="test_api" name="test_get_preferences" time="0.123">
<failure message="Expected 200, got 404" type="AssertionError">
Expected status code to be 200, but got 404
</failure>
</testcase>
<testcase classname="test_cache" name="test_cache_hit" time="0.345">
<system-out>
Cache key: user:123:preferences
Cache hit: false
Cache miss: true
</system-out>
</testcase>
</testsuite>
A common failure is a test that passes locally but fails in CI due to environment differences. The engineer learns that pytest in CI uses pytest.ini with different settings than the local pyproject.toml:
[tool:pytest]
testpaths = tests
addopts = --cov --cov-report=xml --cov-report=html
python_files = test_*.py
python_classes = Test*
python_functions = test_*
By the end of the first month, the engineer is expected to own a small feature or bug. They must define and implement a change, then present it in an architecture review.
The process begins with a decision record in decisions/, a standard directory in the repository:
# Decision: Add rate limiting to API gateway
- **Status**: Implemented
- **Context**: API gateway receives 10K RPS; some services are overloaded
- **Decision**: Use token bucket algorithm with Redis
- **Consequences**:
- Increased request latency by 20ms on average
- Reduced downstream load by 40%
- Requires Redis cluster (already available)
- **Implementation**:
- Add `RateLimiter` middleware in `middleware/rate_limiter.py`
- Configure via `config.yaml`:
```yaml
rate_limiter:
algorithm: token_bucket
burst: 100
refill_rate: 10
redis_url: redis://redis:6379
The engineer presents the decision in a 25-minute session, using a shared `zoom` call with a `screen share` of a `notion` document. They must answer two questions:
1. What happens when Redis is unavailable during rate limiting?
2. How do you handle a burst of 500 requests in one second?
The team expects the engineer to explain that when Redis is down, the rate limiter falls back to a local in-memory bucket, but with a 10-second delay before the next burst is processed. They also explain that the `refill_rate` controls how quickly tokens are added to the bucket, and that `burst` sets the maximum number of tokens.
### Month 2–3: Cross-Team Collaboration
By the second month, the engineer is expected to contribute to cross-team initiatives. They participate in a bi-weekly architecture review board meeting.
The meeting agenda is a `meeting-agenda.md` file in the repository:
```markdown
## Architecture Review Board – 2024-06-15
### Review: User profile service
- **Proposer**: Jane Doe (@jane)
- **Summary**: Migrate user profile service from monolith to microservice
- **Reviewers**: Alex, Sam, Taylor
- **Status**: Draft
### Agenda
1. 10 min: Presentation by Jane
2. 15 min: Discussion
3. 10 min: Voting
4. 5 min: Action items
### Decision Record
- [Link to decision record](https://github.com/acme/eng-platform/decisions/002-user-profile-migration.md)
The engineer must submit a pull request to update the decision record with a comment that includes a TODO list:
## TODO
- [x] Add high-level architecture diagram
- [ ] Add performance benchmarks
- [ ] Define rollback plan
- [ ] Add CI/CD pipeline diagram
- [ ] Finalize deployment strategy
They learn that a “review” is not just a code review, but a formal process where the reviewer evaluates the decision against criteria:
The most common failure is missing the “implementation” section: a decision is accepted but the team realizes the engineer forgot to define how the service will be deployed.
A standard onboarding-checklist.md file guides the journey:
# Engineering Onboarding Checklist
## First Week
- [x] Laptop setup complete
- [ ] Run `make bootstrap`
- [ ] Submit first PR
- [ ] Attend team stand-up
- [ ] Join Slack channels: #engineering, #product, #ops
## First Month
- [ ] Own a feature or bug
- [ ] Write a decision record
- [ ] Present to architecture review board
- [ ] Attend sprint planning
- [ ] Complete documentation task
## First 90 Days
- [ ] Deliver a feature end-to-end
- [ ] Mentor a new engineer
- [ ] Create a reusable component or script
- [ ] Present at team meeting
- [ ] Receive feedback from manager
Each checkbox is linked to a specific task, document, or code change. The checklist is updated in real time via git, with a git commit message that includes the checklist item:
git add onboarding-checklist.md
git commit -m "Add: Complete first sprint planning (Month 1)"
config.yaml: Engineers often forget to copy config.yaml.example to config.yaml, leading to missing environment variables and default values.migrate.sh script does not exit with a non-zero code on error. The engineer learns that psql -c returns 0 even if a query fails, so they must add set -e to the script.feature/new-login instead of main. The engineer learns that the CI workflow is triggered only on push and pull_request to main, develop, and release/* branches.docker-compose logs <service>. They learn that docker-compose up -d starts services in the background, but logs must be manually followed with docker-compose logs -f.At 90 days, the engineer participates in a feedback session with their manager and peers. The feedback is documented in a feedback/90-day-review.json file:
{
"engineer": "Alex Chen",
"date": "2024-06-15",
"strengths": [
"Strong problem-solving skills",
"Good documentation",
"Proactive communication"
],
"areas_for_growth": [
"Better understanding of the deployment pipeline",
"More experience with distributed systems",
"Improved time estimation"
],
"next_steps": [
"Lead a sprint planning",
"Contribute to architecture review board",
"Design and implement a new service"
],
"feedback_from": [
"Taylor (manager)",
"Sam (peer)",
"Jane (mentor)"
]
}
This feedback is used to update the engineer’s career ladder profile and to shape the next onboarding cycle.
Onboarding is not a checklist. It is a journey of ownership, feedback, and discovery—where each engineer becomes a contributor, not just a task executor.
This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy