Building for Production
What Building for Production Changed About How I Build
Developing applications exclusively on `localhost` hides operational reality. On a local development machine, network latency is non-existent, database connections never time out, environment variables are pre-configured in local shell profiles, and server memory feels unconstrained.
Building and deploying Trajectory—a full-stack application deployed on AWS EC2, AWS RDS (PostgreSQL), and Amazon S3 using Docker Compose, Nginx reverse proxying, and GitHub Actions CI/CD pipelines—fundamentally altered how I approach software development.
1 / Codified Environments over Manual Setup
On `localhost`, setting up database schemas, environment variables, and dependencies is done manually. In production, manual steps lead to deployment failures and environment drift.
- Dockerfiles & Compose Manifests: Replacing manual server setup with containerized manifests that define exact runtime images, environment variables, dependencies, and network ports.
- Nginx Reverse Proxying: Configuring SSL termination, CORS headers, gzip compression, and static asset caching explicitly.
- S3 Asset Offloading: Decoupling file uploads from local container file systems to stateless S3 object storage.
2 / CI/CD as an Engineering Guardrail
Manual deployments are error-prone. Integrating GitHub Actions CI/CD pipelines enforced automated testing, container builds, and deployment verification before code merged into production.
Every commit pushed to main branches triggers automated pipeline workflows:
1. Linting & Type Checking: Validating TypeScript types and ESLint syntax rules.
2. Automated Unit & Integration Tests: Executing backend test suites to verify API contracts before building images.
3. Container Build & Push: Building container images and pushing them to registry repositories.
4. Zero-Downtime Deployment: Triggering remote SSH deployment scripts on production servers.
3 / How Production Changed Local Habits
Experiencing production operations changed how I write code locally from day one:
- Explicit Error Handling: Writing explicit try/catch blocks, database retry logic, and fallback responses rather than assuming happy-path success.
- Structured Logging: Emitting JSON formatted logs with timestamps and log levels rather than arbitrary print statements.
- Environment Isolation: Keeping local development environments isolated in Docker containers that mirror production configurations.
Workflow evolution: Production experience changes local development—software is built with environment isolation, configuration management, explicit failure boundaries, and automated verification from the very first commit.
