.PHONY: dev build test test-backend test-frontend test-integration test-e2e lint down clean logs ps ci-validate promote-validate obs-up obs-down obs-ps obs-logs network-init
network-init:
@docker network inspect personal-lifecycle-observability-net >/dev/null 2>&1 || docker network create personal-lifecycle-observability-net
dev: network-init
docker compose up --build
up: network-init
docker compose up -d --build
build: network-init
docker compose build
test: test-backend test-frontend test-integration test-e2e
test-backend: network-init
docker compose run --rm backend pytest tests -m "not integration"
test-integration: network-init
docker compose run --rm backend pytest tests/integration -v
test-frontend: network-init
docker compose run --rm frontend npm test
test-e2e: network-init
docker compose up --exit-code-from e2e e2e
lint: network-init
docker compose run --rm frontend npm run lint
security-audit: network-init
docker run --rm -v $(CURDIR):/src returntocorp/semgrep semgrep --config=p/python --config=p/secrets --config=p/jwt --error /src/backend
docker compose run --rm -T --no-deps frontend npm audit --audit-level=high
setup-hooks:
@mkdir -p .git/hooks
@cp scripts/pre-commit-security.sh .git/hooks/pre-commit
@chmod +x .git/hooks/pre-commit
@echo "Git security pre-commit hook installed successfully."
ps:
docker compose ps
db-shell:
docker compose exec db psql -U postgres -d lifecycle
test-postgres: network-init
docker compose run --rm -e DATABASE_URL=postgresql://postgres:postgres@db:5432/lifecycle backend pytest
logs:
docker compose logs -f
down:
docker compose down -v
clean:
docker compose down -v --rmi local --remove-orphans
# Production Stack (Local or VM)
prod-up:
docker compose -f docker-compose.prod.yml up -d --build
prod-down:
docker compose -f docker-compose.prod.yml down -v
prod-ps:
docker compose -f docker-compose.prod.yml ps
prod-logs:
docker compose -f docker-compose.prod.yml logs -f
# Observability Stack (Decoupled Compose Project: OTel Collector, Prometheus, Loki, Tempo, Grafana)
obs-up:
docker compose -f observability/docker-compose.yaml up -d
obs-down:
docker compose -f observability/docker-compose.yaml down -v
obs-ps:
docker compose -f observability/docker-compose.yaml ps
obs-logs:
docker compose -f observability/docker-compose.yaml logs -f
# Registry (ECR) Image Stages & Pattern Tagging (YYYYMMDD-HHMMSS-shortsha)
IMAGE_TAG ?= $(shell date -u +'%Y%m%d-%H%M%S')-$(shell git rev-parse --short=7 HEAD 2>/dev/null || echo "latest")
ECR_REGISTRY ?= localhost:5000
BACKEND_REPO ?= personal-lifecycle-tracker-backend
FRONTEND_REPO ?= personal-lifecycle-tracker-frontend
ecr-tag:
@echo $(IMAGE_TAG)
build-images:
docker build --target base -t $(ECR_REGISTRY)/$(BACKEND_REPO):$(IMAGE_TAG) -t $(ECR_REGISTRY)/$(BACKEND_REPO):latest ./backend
docker build --target prod -t $(ECR_REGISTRY)/$(FRONTEND_REPO):$(IMAGE_TAG) -t $(ECR_REGISTRY)/$(FRONTEND_REPO):latest ./frontend
push-images:
docker push $(ECR_REGISTRY)/$(BACKEND_REPO):$(IMAGE_TAG)
docker push $(ECR_REGISTRY)/$(BACKEND_REPO):latest
docker push $(ECR_REGISTRY)/$(FRONTEND_REPO):$(IMAGE_TAG)
docker push $(ECR_REGISTRY)/$(FRONTEND_REPO):latest
pull-images:
docker pull $(ECR_REGISTRY)/$(BACKEND_REPO):$(IMAGE_TAG)
docker pull $(ECR_REGISTRY)/$(FRONTEND_REPO):$(IMAGE_TAG)
prod-serve:
BACKEND_IMAGE=$(ECR_REGISTRY)/$(BACKEND_REPO):$(IMAGE_TAG) FRONTEND_IMAGE=$(ECR_REGISTRY)/$(FRONTEND_REPO):$(IMAGE_TAG) docker compose -f docker-compose.prod.yml up -d --no-build
# Infrastructure as Code (IaC) - Zero Host Dependencies via Docker Containers
TF_IMAGE ?= hashicorp/terraform:1.7.5
AWS_CLI_IMAGE ?= amazon/aws-cli:latest
tf-fmt:
docker run --rm -v $(CURDIR):/workspace -w /workspace $(TF_IMAGE) fmt -recursive terraform/
# AWS Terraform - Dev Environment
tf-init-aws:
docker run --rm -v $(CURDIR)/terraform/aws:/workspace -w /workspace $(TF_IMAGE) init
tf-validate-aws:
docker run --rm -v $(CURDIR)/terraform/aws:/workspace -w /workspace $(TF_IMAGE) validate
tf-plan-aws:
docker run --rm -v $(CURDIR)/terraform/aws:/workspace -w /workspace -v $$HOME/.aws:/root/.aws:ro -e AWS_PROFILE $(TF_IMAGE) plan -var-file=terraform.tfvars.dev
tf-apply-aws:
docker run --rm -it -v $(CURDIR)/terraform/aws:/workspace -w /workspace -v $$HOME/.aws:/root/.aws:ro -e AWS_PROFILE $(TF_IMAGE) apply -var-file=terraform.tfvars.dev
# AWS Terraform - Production Environment Copy
tf-init-aws-prod:
docker run --rm -v $(CURDIR)/terraform/aws-prod:/workspace -w /workspace $(TF_IMAGE) init
tf-validate-aws-prod:
docker run --rm -v $(CURDIR)/terraform/aws-prod:/workspace -w /workspace $(TF_IMAGE) validate
tf-plan-aws-prod:
docker run --rm -v $(CURDIR)/terraform/aws-prod:/workspace -w /workspace -v $$HOME/.aws:/root/.aws:ro -e AWS_PROFILE $(TF_IMAGE) plan -var-file=terraform.tfvars.prod
tf-apply-aws-prod:
docker run --rm -it -v $(CURDIR)/terraform/aws-prod:/workspace -w /workspace -v $$HOME/.aws:/root/.aws:ro -e AWS_PROFILE $(TF_IMAGE) apply -var-file=terraform.tfvars.prod
# Render Terraform - Dev Environment
tf-init-render:
docker run --rm -v $(CURDIR)/terraform/render:/workspace -w /workspace $(TF_IMAGE) init
tf-validate-render:
docker run --rm -v $(CURDIR)/terraform/render:/workspace -w /workspace $(TF_IMAGE) validate
tf-plan-render:
docker run --rm -v $(CURDIR)/terraform/render:/workspace -w /workspace -e RENDER_API_KEY $(TF_IMAGE) plan -var-file=terraform.tfvars.dev
# Render Terraform - Production Environment Copy
tf-init-render-prod:
docker run --rm -v $(CURDIR)/terraform/render-prod:/workspace -w /workspace $(TF_IMAGE) init
tf-validate-render-prod:
docker run --rm -v $(CURDIR)/terraform/render-prod:/workspace -w /workspace $(TF_IMAGE) validate
tf-plan-render-prod:
docker run --rm -v $(CURDIR)/terraform/render-prod:/workspace -w /workspace -e RENDER_API_KEY $(TF_IMAGE) plan -var-file=terraform.tfvars.prod
# AWS Serverless (Option C: Lambda + Neon + CloudFront)
tf-init-serverless:
docker run --rm -v $(CURDIR)/terraform/aws-serverless:/workspace -w /workspace $(TF_IMAGE) init
tf-validate-serverless:
docker run --rm -v $(CURDIR)/terraform/aws-serverless:/workspace -w /workspace $(TF_IMAGE) validate
tf-plan-serverless:
docker run --rm -v $(CURDIR)/terraform/aws-serverless:/workspace -w /workspace -v $$HOME/.aws:/root/.aws:ro -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN -e AWS_DEFAULT_REGION -e AWS_PROFILE $(TF_IMAGE) plan
tf-apply-serverless:
docker run --rm -it -v $(CURDIR)/terraform/aws-serverless:/workspace -w /workspace -v $$HOME/.aws:/root/.aws:ro -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN -e AWS_DEFAULT_REGION -e AWS_PROFILE $(TF_IMAGE) apply
deploy-serverless:
bash terraform/aws-serverless/deploy.sh
# Validate All Terraform Environments
tf-validate: tf-validate-aws tf-validate-aws-prod tf-validate-render tf-validate-render-prod tf-validate-serverless
# CloudFormation Validation
cf-validate-dev:
docker run --rm -v $(CURDIR)/cloudformation:/cf -v $$HOME/.aws:/root/.aws:ro -e AWS_DEFAULT_REGION=$(AWS_DEFAULT_REGION) $(AWS_CLI_IMAGE) cloudformation validate-template --template-body file:///cf/aws-serverless-lambda.yaml
cf-validate-prod:
docker run --rm -v $(CURDIR)/cloudformation:/cf -v $$HOME/.aws:/root/.aws:ro -e AWS_DEFAULT_REGION=$(AWS_DEFAULT_REGION) $(AWS_CLI_IMAGE) cloudformation validate-template --template-body file:///cf/aws-serverless-lambda-prod.yaml
cf-validate: cf-validate-dev cf-validate-prod
# CloudFormation Deploy & Lifecycle Targets
CF_STACK_DEV ?= personal-lifecycle-tracker-dev
CF_STACK_PROD ?= personal-lifecycle-tracker-prod
AWS_DEFAULT_REGION ?= us-east-1
cf-deploy-dev:
docker run --rm -v $(CURDIR)/cloudformation:/cf -v $$HOME/.aws:/root/.aws:ro \
-e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN -e AWS_DEFAULT_REGION=$(AWS_DEFAULT_REGION) \
$(AWS_CLI_IMAGE) cloudformation create-stack \
--stack-name $(CF_STACK_DEV) \
--template-body file:///cf/aws-serverless-lambda.yaml \
--parameters ParameterKey=Environment,ParameterValue=dev ParameterKey=ProjectName,ParameterValue=personal-lifecycle-tracker \
--capabilities CAPABILITY_NAMED_IAM \
--region $(AWS_DEFAULT_REGION)
cf-deploy-prod:
docker run --rm -v $(CURDIR)/cloudformation:/cf -v $$HOME/.aws:/root/.aws:ro \
-e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN -e AWS_DEFAULT_REGION=$(AWS_DEFAULT_REGION) \
$(AWS_CLI_IMAGE) cloudformation create-stack \
--stack-name $(CF_STACK_PROD) \
--template-body file:///cf/aws-serverless-lambda-prod.yaml \
--parameters ParameterKey=Environment,ParameterValue=production ParameterKey=ProjectName,ParameterValue=personal-lifecycle-tracker \
--capabilities CAPABILITY_NAMED_IAM \
--region $(AWS_DEFAULT_REGION)
cf-status-dev:
docker run --rm -v $$HOME/.aws:/root/.aws:ro \
-e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN -e AWS_DEFAULT_REGION=$(AWS_DEFAULT_REGION) \
$(AWS_CLI_IMAGE) cloudformation describe-stacks --stack-name $(CF_STACK_DEV) --region $(AWS_DEFAULT_REGION)
cf-status-prod:
docker run --rm -v $$HOME/.aws:/root/.aws:ro \
-e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN -e AWS_DEFAULT_REGION=$(AWS_DEFAULT_REGION) \
$(AWS_CLI_IMAGE) cloudformation describe-stacks --stack-name $(CF_STACK_PROD) --region $(AWS_DEFAULT_REGION)
ci-validate:
act -n -W .github/workflows/ci.yml
promote-validate:
act -n -W .github/workflows/promote-dev-to-prod.yml
# On-Call Engineer & Autonomous Incident Response
on-call-poll:
python3 on-call-engineer/poll-alerts.py
on-call-verify:
bash on-call-engineer/scripts/verify
on-call-simulate-cloud:
bash on-call-engineer/cloud-simulator/scripts/run-cloud-simulation
on-call-aws-drill:
bash on-call-engineer/scripts/run-aws-incident-drill
on-call-code-fix-drill:
bash on-call-engineer/scripts/run-code-fix-drill
# Local Kubernetes Deployment with Kind
KIND_CLUSTER_NAME ?= lifecycle-cluster
kind-up:
kind create cluster --name $(KIND_CLUSTER_NAME) || true
kind-load:
docker build -t personal-lifecycle-backend:latest ./backend
docker build -t personal-lifecycle-frontend:latest -f frontend/Dockerfile --target prod ./frontend
kind load docker-image personal-lifecycle-backend:latest --name $(KIND_CLUSTER_NAME)
kind load docker-image personal-lifecycle-frontend:latest --name $(KIND_CLUSTER_NAME)
kind-deploy:
kubectl apply -k k8s/
kind-verify:
kubectl rollout status deployment/lifecycle-postgres --timeout=90s
kubectl rollout status deployment/lifecycle-backend --timeout=90s
kubectl rollout status deployment/lifecycle-frontend --timeout=90s
kind-down:
kind delete cluster --name $(KIND_CLUSTER_NAME)