peopleanalyst

library / lib6130a2990f6dc37b

Bootstrapping Microservices, Second Edition With Docker, Kubernetes, GitHub Actions, and Terraform

Ashley Davis

In a sentence

A hands-on, project-driven guide that walks developers from zero to a fully deployed, production-ready microservices application using Docker, Kubernetes, GitHub Actions, and Terraform.

Bootstrapping Microservices, Second Edition is the practical antidote to theoretical microservices books that leave developers wondering where to start. Author Ashley Davis guides readers through building FlixTube—a complete video-streaming application—from a single Node.js microservice all the way to a cloud-hosted Kubernetes cluster with automated CI/CD pipelines. Each chapter introduces the minimum tooling necessary (Docker, Docker Compose, Kubernetes, Terraform, GitHub Actions, Jest, Playwright) in carefully sequenced, working examples. Readers learn how to containerize services, manage data with MongoDB and Azure Storage, wire microservices together via HTTP and RabbitMQ, codify infrastructure with Terraform, automate deployments with GitHub Actions, and apply unit, integration, and end-to-end testing—all while keeping complexity manageable through iterative, keep-it-working development philosophy. Rather than prescribing dogma, the book positions microservices on a spectrum of architectural choices and equips readers with tools to choose freely.

The four lenses

  • Science
  • Statistics
  • Systems
  • Strategy

Tags

software-engineeringf1-systems

The model

A causal model describing how design levers (architectural choices, tooling adoption, automation investment, and development practices) shape psychological and behavioral states in development teams, which in turn drive delivery and system quality outcomes when building microservices applications.

Microservice Decompositiondesign lever

The degree to which an application's functionality is divided into small, independently deployable services with hard process boundaries, each owning a single area of business responsibility and its own data store, as opposed to a monolithic single-process architecture.

Containerization Adoptiondesign lever

The extent to which microservices are packaged as Docker images with explicit Dockerfiles for both development and production modes, published to a private container registry, and deployed as containers rather than directly on bare-metal or virtual-machine processes.

Infrastructure as Codedesign lever

The practice of defining, versioning, and executing cloud infrastructure (resource groups, container registries, Kubernetes clusters, networking) through declarative code files (e.g., Terraform HCL) rather than through manual GUI interactions or ad-hoc CLI commands, enabling repeatable and automated environment creation.

CI/CD Pipeline Investmentdesign lever

The degree to which automated workflows (e.g., GitHub Actions) are implemented to continuously build, test, and deploy microservices in response to code pushes, eliminating manual build and deployment steps and providing a reliable, auditable delivery conduit from code commit to production.

Automated Test Coveragedesign lever

The breadth and depth of automated tests applied across unit, integration, and end-to-end levels for individual microservices and the overall application, including use of mocking, database fixtures, and browser-level simulation to validate behavior without manual intervention.

Iterative Development Practicedesign lever

The discipline of making small, well-tested code changes in rapid succession—keeping code in a working state at each step, starting from the simplest possible implementation, and using live reload tooling (nodemon, Docker Compose) to minimize feedback latency between code change and observable result.

Loose Coupling Designdesign lever

The architectural property whereby microservices minimize direct dependencies on each other—preferring indirect messaging via message queues (RabbitMQ), well-defined REST APIs, and database isolation—so that individual services can be updated, replaced, or scaled without requiring coordinated changes to other services.

Team Skill Levelcontextual condition

The collective technical proficiency of the development team in the specific toolchain required for microservices—Docker, Kubernetes, Terraform, CI/CD tooling, distributed systems design, and automated testing—which determines the speed and fidelity with which design levers can be applied and the degree to which complexity is manageable rather than overwhelming.

Application Complexitycontextual condition

The inherent difficulty of the problem domain and the number of interacting microservices, data stores, messaging flows, and external integrations in the application, which determines how much the microservices architecture's benefits (hard boundaries, independent scalability) are needed and how much additional operational overhead they impose.

Deployment Confidencepsychological state

The degree to which developers feel certain that pushing code changes to production is safe, predictable, and reversible—driven by automated testing, automated pipelines, rollback capabilities, and environment parity—reducing deployment fear and enabling frequent, low-risk releases.

Developer Autonomypsychological state

The extent to which individual developers or small teams can independently develop, test, deploy, and operate their microservices without requiring coordination with other teams or waiting for a centralized operations function, enabled by process boundaries, independent deployment schedules, and self-service tooling.

Complexity Manageabilitypsychological state

The perceived and objective ability of the development team to understand, modify, and extend the application without being overwhelmed—achieved through hard service boundaries, small and focused codebases per service, and tooling that makes the overall system's behavior visible and controllable.

Deployment Frequencyoutcome metric

The rate at which new versions of microservices are successfully delivered to the production environment, reflecting the team's ability to release small, incremental changes continuously rather than accumulating large risky releases—a core DORA metric enabled by automated CI/CD pipelines and small service boundaries.

System Reliabilityoutcome metric

The operational stability of the microservices application in production, including fault isolation (failures in one service do not cascade), automatic recovery (Kubernetes restarts failed pods), graceful degradation, and the ability to maintain service availability during rolling updates and deployments.

Scalability Capacityoutcome metric

The architectural readiness and operational capability of the application to handle increased load—through horizontal scaling of individual microservices, elastic cluster scaling, database scaling, and independent scaling of high-demand services—without requiring full application rewrites or downtime.

Development Paceoutcome metric

The speed at which the development team can deliver new features, fix bugs, and respond to changing business requirements—enabled by small codebases per service, independent deployment schedules, live reload tooling, and automated pipelines that reduce manual toil and accelerate feedback loops.

Infrastructure Reproducibilityoutcome metric

The capability to reliably recreate identical production-equivalent environments (container registry, Kubernetes cluster, networking, IAM roles) from version-controlled code with a single command, enabling blue-green deployments, parallel environments for testing and production, and disaster recovery without manual configuration.

How they connect

  • microservice decomposition predicts complexity manageability
  • microservice decomposition predicts scalability capacity
  • microservice decomposition predicts developer autonomy
  • loose coupling design predicts complexity manageability
  • loose coupling design predicts system reliability
  • containerization adoption predicts deployment confidence
  • containerization adoption predicts infrastructure reproducibility
  • infrastructure as code predicts infrastructure reproducibility
  • cicd pipeline investment predicts deployment confidence
  • cicd pipeline investment predicts deployment frequency
  • automated test coverage predicts deployment confidence
  • automated test coverage predicts system reliability
  • iterative development practice predicts deployment confidence
  • iterative development practice predicts development pace
  • deployment confidence predicts deployment frequency
  • deployment confidence predicts development pace
  • developer autonomy predicts deployment frequency
  • complexity manageability predicts development pace
  • team skill level moderates microservice decomposition
  • team skill level moderates cicd pipeline investment
  • application complexity moderates microservice decomposition
  • deployment frequency predicts system reliability
  • infrastructure reproducibility predicts system reliability

The process

The book provides a comprehensive, hands-on playbook for building, deploying, and managing microservices applications from the ground up. The overall strategy begins with establishing a local development workflow for a single microservice using Node.js, emphasizing rapid iteration with live reloading. This single service is then containerized using Docker, establishing a repeatable packaging and publishing process to a cloud container registry. From this single-service foundation, the playbook expands to a multi-service architecture managed locally with Docker Compose. This local simulation of the production environment incorporates third-party services like databases (MongoDB) and message queues (RabbitMQ), and teaches methods for both direct (HTTP) and indirect (messaging) communication between services. The core development loop is enhanced with application-wide live reloading to maintain high productivity. The final phase of the playbook focuses on production readiness and automation. It guides the practitioner through creating cloud infrastructure (a Kubernetes cluster and container registry) as code using Terraform, ensuring repeatability and version control. This infrastructure becomes the target for a fully automated CI/CD pipeline built with GitHub Actions, which automatically tests and deploys any code changes. The entire process is supported by a multi-layered automated testing strategy—covering unit, integration, and end-to-end tests—to ensure the application's reliability and quality as it evolves.

Local Single-Service Development

To establish a productive local development environment for creating and testing an individual microservice before integrating it into a larger application.

When to use: When starting a new microservice or when debugging or adding features to an existing one in isolation.

  1. Step 1Set up the development environment.

    Entry: A development machine is available.

    Exit: All required tools are installed and their versions are verified.

    Out: A configured development environment

  2. Step 2Create a new Node.js project.

    Entry: Node.js and npm are installed.

    Exit: A valid package.json file exists in the project directory.

    Out: package.json file

  3. Step 3Write the microservice code.

    Entry: A Node.js project is initialized.

    Exit: The microservice has functional source code.

    Out: Microservice source code

  4. Step 4Configure the microservice using environment variables.

    Entry: Microservice code is written.

    Exit: Key parameters like port numbers are configurable via environment variables.

    Out: Configurable microservice code

  5. Step 5Set up live reload for rapid iteration.

    Entry: The microservice is runnable.

    Exit: A 'start:dev' script that enables live reload is present in package.json.

    Out: package.json with dev script

  6. Step 6Run and test the microservice locally.

    Entry: The microservice code and dev scripts are complete.

    Exit: The microservice runs locally and passes manual tests.

    In: Environment variables · Out: A running microservice process

Package and Publish a Microservice with Docker

To create a standardized, portable, and versioned artifact (a Docker image) of a microservice and publish it to a central registry, making it ready for deployment.

When to use: After a microservice has been developed and is ready to be prepared for staging or production deployment.

  1. Step 1Create a production Dockerfile.

    Entry: A functional microservice project exists.

    Exit: A valid Dockerfile for production is present in the project.

    In: Microservice source code, package.json · Out: Dockerfile

  2. Step 2Build the Docker image.

    Entry: Docker is installed and a Dockerfile exists.

    Exit: A new Docker image is created and visible via 'docker image list'.

    In: Dockerfile, Project source code · Out: Docker image

  3. Step 3Create a private container registry.

    Entry: A cloud provider account exists.

    Exit: A container registry is available and its credentials are known.

    Out: Container registry URL and credentials

  4. Step 4Tag the image for the registry.

    Entry: A local Docker image and a container registry exist.

    Exit: The image has a new tag that includes the registry URL.

    In: Local Docker image, Container registry URL · Out: Tagged Docker image

  5. Step 5Log in to the container registry.

    Entry: Registry credentials are known.

    Exit: The Docker client is authenticated with the remote registry.

    In: Container registry credentials

  6. Step 6Push the image to the registry.

    Entry: The image is tagged and the client is authenticated.

    Exit: The image is available in the remote container registry.

    In: Tagged Docker image · Out: Published Docker image in registry

Local Multi-Service Development with Docker Compose

To define, build, and run a complete multi-service application (including microservices and backing services like databases) in an isolated environment on a local machine for development and testing.

When to use: When the application grows beyond a single service and requires orchestration of multiple components for local development.

  1. Step 1Create a `docker-compose.yaml` file.

    Entry: The project contains multiple services, each with its own Dockerfile.

    Exit: A `docker-compose.yaml` file exists.

    Out: docker-compose.yaml

  2. Step 2Define each microservice in the Compose file.

    Entry: A `docker-compose.yaml` file exists.

    Exit: All custom microservices are defined as services in the Compose file.

    In: Dockerfiles for each service · Out: Updated docker-compose.yaml

  3. Step 3Add backing services like databases and message queues.

    Entry: The application requires backing services.

    Exit: Backing services are defined in the Compose file.

    Out: Updated docker-compose.yaml

  4. Step 4Configure application-wide live reload.

    Entry: Rapid iteration is desired for development.

    Exit: Code changes on the host machine are reflected in running containers without a full rebuild.

    Out: Updated docker-compose.yaml, Dockerfile-dev for each service

  5. Step 5Boot the entire application.

    Entry: The `docker-compose.yaml` file is complete.

    Exit: All defined services are running and accessible.

    In: docker-compose.yaml · Out: A running multi-container application

  6. Step 6Shut down the application.

    Entry: The application is running.

    Exit: All application resources are removed from the Docker engine.

Manual Deployment to Kubernetes

To deploy a containerized microservice to a Kubernetes cluster, making it run in a production-like environment and become accessible over the network.

When to use: When first learning Kubernetes, testing a new cluster, or performing an initial, one-off deployment.

  1. Step 1Prepare the Kubernetes cluster.

    Entry: A cloud account or Docker Desktop is available.

    Exit: A running Kubernetes cluster is accessible.

    • Use a local cluster or a cloud-based cluster?

    Out: A Kubernetes cluster

  2. Step 2Install and configure kubectl.

    Entry: A Kubernetes cluster exists.

    Exit: 'kubectl get nodes' successfully communicates with the cluster.

    In: Cluster access credentials · Out: A configured kubectl context

  3. Step 3Publish the microservice Docker image.

    Entry: The microservice has a production Dockerfile.

    Exit: The Docker image is available in a container registry.

    Out: Published Docker image

  4. Step 4Create a Kubernetes deployment YAML file.

    Entry: The image URL is known.

    Exit: A valid 'deploy.yaml' file exists.

    In: Image URL · Out: deploy.yaml

  5. Step 5Apply the configuration to the cluster.

    Entry: kubectl is configured and 'deploy.yaml' is ready.

    Exit: Kubernetes acknowledges the creation of the deployment and service.

    In: deploy.yaml · Out: Kubernetes objects (Deployment, Pod, Service)

  6. Step 6Verify and test the deployment.

    Entry: The configuration has been applied.

    Exit: The microservice is running in the cluster and responds to requests.

  7. Step 7Clean up the deployment.

    Entry: The deployment is no longer needed.

    Exit: The microservice's resources are removed from the cluster.

    In: deploy.yaml

Infrastructure as Code with Terraform

To define and provision cloud infrastructure (like container registries and Kubernetes clusters) in a repeatable, automated, and version-controlled manner using declarative code.

When to use: When moving beyond manual infrastructure creation to establish a reliable and automated foundation for production and staging environments.

  1. Step 1Set up the Terraform project.

    Entry: A cloud provider account is available.

    Exit: Terraform is installed and authenticated.

  2. Step 2Define providers and variables.

    Entry: A Terraform project directory exists.

    Exit: Provider and variable definitions are in place.

    Out: providers.tf, variables.tf

  3. Step 3Define infrastructure resources in .tf files.

    Entry: The required infrastructure components are known.

    Exit: All infrastructure is declaratively defined in code.

    In: variables.tf · Out: Resource definition .tf files

  4. Step 4Initialize the Terraform project.

    Entry: Provider definitions exist in 'providers.tf'.

    Exit: The '.terraform' directory is populated with provider plugins.

    In: providers.tf

  5. Step 5Apply the configuration to create infrastructure.

    Entry: The project is initialized and resource definitions are complete.

    Exit: The cloud infrastructure is created and matches the code definition. A 'terraform.tfstate' file is created.

    In: All .tf files · Out: Live cloud infrastructure, terraform.tfstate file

  6. Step 6Destroy the infrastructure when finished.

    Entry: The infrastructure is no longer needed.

    Exit: All created cloud resources are deleted.

    In: terraform.tfstate file

Automated CI/CD with GitHub Actions

To create a fully automated pipeline that builds, tests, and deploys a microservice to Kubernetes whenever code is pushed to the repository, enabling continuous deployment.

When to use: When manual deployments become too slow, error-prone, or frequent, and a reliable, automated release process is needed.

  1. Step 1Create shell scripts for deployment stages.

    Entry: The manual steps for build, push, and deploy are known.

    Exit: A set of shell scripts that can deploy the microservice from the command line exists.

    Out: Deployment shell scripts

  2. Step 2Template the Kubernetes deployment configuration.

    Entry: A static 'deploy.yaml' exists.

    Exit: The Kubernetes configuration is a template that can be populated at deploy time.

    Out: Templated deploy.yaml

  3. Step 3Store sensitive credentials as GitHub Secrets.

    Entry: All necessary credentials for deployment are known.

    Exit: Credentials are securely stored in GitHub Secrets and are not in the codebase.

    In: Cloud credentials · Out: GitHub Secrets

  4. Step 4Create a GitHub Actions workflow file.

    Entry: The repository is hosted on GitHub.

    Exit: A workflow YAML file exists in the correct directory.

    Out: Workflow YAML file

  5. Step 5Define the deployment job and steps.

    Entry: The workflow file is created.

    Exit: The workflow file contains all steps to build, test, and deploy the microservice.

    In: GitHub Secrets, Deployment shell scripts · Out: Complete workflow YAML file

  6. Step 6Trigger and monitor the workflow.

    Entry: The workflow file is committed to the repository.

    Exit: The workflow runs successfully, and the new version of the microservice is deployed to Kubernetes.

    In: Code change · Out: A deployed microservice

Automated Testing Strategy

To implement a multi-layered suite of automated tests to ensure code quality, prevent regressions, and enable safe, continuous refactoring and deployment of microservices.

When to use: Throughout the development lifecycle, especially before committing code and as a required check in a CI/CD pipeline.

  1. Step 1Implement unit tests.

    Entry: A function or module with clear inputs and outputs exists.

    Exit: A suite of fast-running tests verifies the logic of the code unit.

    In: Source code of a function/module · Out: Unit test suite

  2. Step 2Implement integration tests.

    Entry: A microservice with external dependencies (e.g., a database) exists.

    Exit: Tests verify that the microservice integrates correctly with its direct dependencies.

    In: Microservice code, Database fixtures · Out: Integration test suite

  3. Step 3Implement end-to-end (E2E) tests.

    Entry: A user-facing frontend and its backing microservices exist.

    Exit: Tests verify that critical user journeys work as expected from the user's perspective.

    In: The full application running under Docker Compose · Out: E2E test suite

  4. Step 4Integrate tests into the CI/CD pipeline.

    Entry: An automated test suite and a CI/CD pipeline exist.

    Exit: The CI/CD pipeline automatically gates deployments on the success of the test suite.

    In: Test suites · Out: A quality gate in the deployment pipeline

The story

The reader A software developer or technical architect who wants to build and ship a real microservices application—or convert an existing monolith—but feels overwhelmed by where to start and how to assemble the right toolchain.

External problem

The developer cannot get a microservices application from local code to a production Kubernetes cluster without wading through fragmented, theoretical, or overly complex documentation and tutorials.

Internal problem

They feel stuck, intimidated by the steep learning curve, and afraid of making bad architectural decisions early in the project that will haunt them later.

Philosophical problem

It is wrong that powerful, production-grade microservices tooling should be accessible only to large teams with dedicated platform engineers; every developer deserves a clear, practical path to shipping reliable distributed systems.

The plan

  1. Build and run a single microservice locally with Node.js to establish the development baseline.
  2. Package the microservice as a Docker image and publish it to a private container registry.
  3. Scale to multiple microservices using Docker Compose with live reload for rapid iteration.
  4. Add data management: external file storage (Azure Storage) and a database (MongoDB), each owned by its own microservice.
  5. Wire microservices together using HTTP for direct messaging and RabbitMQ for indirect, decoupled messaging.
  6. Deploy to Kubernetes—first locally via Docker Desktop, then to a managed Azure cluster.
  7. Automate infrastructure creation (container registry, Kubernetes cluster) using Terraform and infrastructure as code.
  8. Build a CI/CD pipeline in GitHub Actions that automatically tests and deploys on every code push.
  9. Apply unit, integration, and end-to-end automated testing with Jest and Playwright.
  10. Review the complete FlixTube application, deploy it fully to production, and plan for scalability, reliability, and security.

Success

  • The developer can confidently create, containerize, and deploy individual microservices using Docker and Kubernetes.
  • They have a fully automated CI/CD pipeline that deploys changes to production on every code push.
  • They can create and tear down cloud infrastructure repeatably using Terraform code.
  • They have a comprehensive automated test suite covering unit, integration, and end-to-end levels.
  • They understand where to position any project on the monolith-to-microservices spectrum and have the tools to act on that choice.
  • They can scale their application—both performance and team size—using proven microservices patterns.

At stake

  • Without automation, manual deployments become the bottleneck that prevents the team from shipping features fast enough to stay competitive.
  • Without the right toolchain and sequencing, developers waste months on failed attempts, make costly early architectural decisions, and end up with a tangled big ball of mud that is harder to fix than a well-structured monolith.
  • Without automated testing, growing the number of microservices quickly becomes unmanageable and regression bugs reach production.
  • Without infrastructure as code, environments drift, onboarding new developers is painful, and disaster recovery is slow and risky.

Questions this book answers

How do I build and run a single microservice from scratch?
How do I package and publish microservices using Docker and a private container registry?
How do I develop and test multiple microservices together locally using Docker Compose?
How do microservices communicate—when should I use HTTP versus RabbitMQ?
How do I manage data (databases and file storage) across microservices?

Glossary

Microservice Decomposition
The architectural decision to divide an application into multiple small, independently deployable processes, each with a single business responsibility and its own isolated data store, as opposed to a monolithic single-process architecture.
Containerization Adoption
The extent to which every microservice is packaged as an immutable Docker image with explicit Dockerfiles for development and production, published to a versioned private container registry, and run exclusively as containers in both development and production environments.
Infrastructure as Code
The practice of defining all cloud infrastructure (networking, compute, storage, identity) in version-controlled, executable code files (Terraform HCL) so that environments can be created, modified, and destroyed programmatically and repeatably without manual intervention.
CI/CD Pipeline Investment
The degree to which automated GitHub Actions workflows (or equivalent) are implemented to build Docker images, run automated tests, and deploy microservices to production in response to code pushes, replacing manual build-publish-deploy sequences with reliable, auditable automation.
Automated Test Coverage
The breadth and depth of code-driven tests across unit, integration, and end-to-end levels that exercise microservice behavior—including mocked dependencies, database fixtures, and browser-level simulations—providing a programmatic safety net that detects regressions before production deployment.
Iterative Development Practice
The developer behavior pattern of making small, focused code changes in rapid succession, keeping the codebase in a continuously working state at each step, starting from the simplest implementation, and using live reload tooling to minimize the time between code change and observable test result.
Loose Coupling Design
The architectural property of a microservices system whereby inter-service dependencies are minimized—achieved through indirect RabbitMQ messaging for notifications, isolated per-service databases, and well-defined REST API boundaries—so that each service can evolve, scale, or be replaced without requiring coordinated changes in other services.
Team Skill Level
The collective technical proficiency of the development team in the specific tools and concepts required for microservices—Docker, Kubernetes, Terraform, CI/CD, distributed systems design, messaging patterns, and automated testing—which determines the effectiveness with which the architectural and tooling levers can be applied.

Related in the library