Environment and Tools

Back to Infrastructure Index

Development Environment Setup

Setting up consistent development environments is crucial for team productivity and reducing “it works on my machine” problems.

Makefile

A Makefile is a build automation tool that helps standardize common tasks across your team.

What is a Makefile?

  • Build automation tool
  • Defines tasks (targets) and their dependencies
  • Commonly used in Unix/Linux environments
  • Simplifies complex command sequences

Basic Makefile Structure

target: dependencies
	command
	command

.PHONY Directive

Purpose: ใช้เขียนบนหัว Makefile เพื่อหลีกเลี่ยง conflict ของไฟล์ที่มีชื่อซ้ำกัน

.PHONY tells Make that these targets are not actual files, but rather command names.

Example:

.PHONY: build test clean run
 
build:
	go build -o app main.go
 
test:
	go test ./...
 
clean:
	rm -f app
 
run:
	./app

Why use .PHONY?

  • Prevents conflicts with files named build, test, etc.
  • Ensures targets always run, even if files with those names exist
  • Makes Makefile more reliable

Practical Makefile Examples

Go Project Makefile

.PHONY: build test clean run docker-build docker-run
 
# Variables
APP_NAME=myapp
DOCKER_IMAGE=myapp:latest
 
# Build the application
build:
	go build -o bin/$(APP_NAME) cmd/main.go
 
# Run tests
test:
	go test -v ./...
 
# Run tests with coverage
test-coverage:
	go test -cover ./...
 
# Clean build artifacts
clean:
	rm -rf bin/
	go clean
 
# Run the application
run: build
	./bin/$(APP_NAME)
 
# Build Docker image
docker-build:
	docker build -t $(DOCKER_IMAGE) .
 
# Run Docker container
docker-run:
	docker run -p 8080:8080 $(DOCKER_IMAGE)
 
# Install dependencies
deps:
	go mod download
	go mod tidy
 
# Format code
fmt:
	go fmt ./...
 
# Lint code
lint:
	golangci-lint run

Node.js Project Makefile

.PHONY: install build test dev clean
 
install:
	npm install
 
build:
	npm run build
 
test:
	npm test
 
dev:
	npm run dev
 
clean:
	rm -rf node_modules/ dist/
 
deploy: build
	./deploy.sh

Docker-focused Makefile

.PHONY: up down build logs shell clean
 
# Start services
up:
	docker-compose up -d
 
# Stop services
down:
	docker-compose down
 
# Build images
build:
	docker-compose build
 
# View logs
logs:
	docker-compose logs -f
 
# Shell into container
shell:
	docker-compose exec app /bin/sh
 
# Clean up everything
clean:
	docker-compose down -v
	docker system prune -f

Environment Variables

What are Environment Variables?

Configuration values that can change between environments without code changes.

Common Use Cases

  • Database connection strings
  • API keys and secrets
  • Feature flags
  • Service URLs
  • Configuration settings

Managing Environment Variables

.env Files

# .env
DATABASE_URL=postgresql://localhost:5432/mydb
API_KEY=secret-key-here
PORT=3000
NODE_ENV=development

Important: Never commit .env files to version control!

# .gitignore
.env
.env.local
.env.*.local

Loading Environment Variables

Bash:

export DATABASE_URL="postgresql://localhost:5432/mydb"
source .env

Node.js:

require('dotenv').config();
const dbUrl = process.env.DATABASE_URL;

Go:

import "os"
 
dbUrl := os.Getenv("DATABASE_URL")

Python:

import os
from dotenv import load_dotenv
 
load_dotenv()
db_url = os.getenv("DATABASE_URL")

Environment-Specific Configuration

.env                 # Default/development
.env.development     # Development specific
.env.staging         # Staging specific
.env.production      # Production specific
.env.test           # Test specific

Configuration Management Best Practices

1. Twelve-Factor App Configuration

Store config in the environment, never in code:

Good:

dbHost := os.Getenv("DB_HOST")

Bad:

dbHost := "localhost" // Hardcoded

2. Hierarchical Configuration

Default values → Environment files → Environment variables → Command-line args
(Lowest priority)                                           (Highest priority)

3. Secret Management

  • Use secret management tools (Vault, AWS Secrets Manager, Azure Key Vault)
  • Never commit secrets to git
  • Rotate secrets regularly
  • Use different secrets per environment

4. Configuration Validation

func validateConfig() error {
    required := []string{"DB_HOST", "DB_USER", "DB_PASSWORD"}
    for _, key := range required {
        if os.Getenv(key) == "" {
            return fmt.Errorf("required env var %s is not set", key)
        }
    }
    return nil
}

Common Development Tools

Version Managers

  • nvm - Node Version Manager
  • gvm - Go Version Manager
  • pyenv - Python Version Manager
  • rbenv - Ruby Version Manager
  • asdf - Universal version manager

Package Managers

  • npm/yarn/pnpm - JavaScript
  • pip/poetry - Python
  • go mod - Go
  • maven/gradle - Java

Build Tools

  • Make - Universal build automation
  • Gradle - Java/Kotlin
  • Webpack - JavaScript bundling
  • Maven - Java dependency management

Infrastructure as Code (IaC)

Tools

  • Terraform - Multi-cloud infrastructure
  • Ansible - Configuration management
  • Kubernetes - Container orchestration
  • Docker Compose - Multi-container Docker apps

Example: Docker Compose

version: '3.8'
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - DATABASE_URL=${DATABASE_URL}
    depends_on:
      - db
  
  db:
    image: postgres:14
    environment:
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - db-data:/var/lib/postgresql/data
 
volumes:
  db-data:

Environment Setup Checklist

Development Environment

  • Install required programming languages
  • Install version managers
  • Set up IDE/editor
  • Configure git
  • Install Docker
  • Set up local databases
  • Configure environment variables
  • Install project dependencies

CI/CD Environment

  • Configure build pipeline
  • Set up test automation
  • Configure deployment scripts
  • Set up environment variables
  • Configure secret management
  • Set up monitoring

Related: