ALEX C. GODWIN | Cloud & DevOps Engineer
CONTAINER ENGINEERING PROJECT

Docker Containerization & Secure Image Delivery

Built and validated a multi-service container delivery system for frontend, API, authentication and worker services. The implementation uses service-specific Dockerfiles, non-root runtime controls, slim base images, immutable release tags, Amazon ECR publication and Kubernetes-ready delivery through the existing CI and GitOps platform.

DockerAmazon ECRJenkinsKubernetesNGINXPythonGitOps

Project Overview

I designed the container layer for a multi-service application consisting of a frontend, API, authentication service and background worker, with PostgreSQL and Redis supporting the application workflow.

The container design was not treated as a simple packaging exercise. I used Docker as the boundary between application code and the delivery platform, then connected those images to Jenkins, Amazon ECR, GitOps and Amazon EKS so the same release identity could be traced from build to runtime.

SERVICES4 Application ImagesFrontend • API • Auth • Worker
RUNTIMENon-root ExecutionUnprivileged NGINX • UID 10001
RELEASEbuild-14Immutable image identity
REGISTRYAmazon ECRPrivate image publication

Engineering Problem

The platform needed more than one generic container image. Each application component had a different runtime responsibility and needed a repeatable build definition, clear process ownership and a release identity that could survive promotion through multiple environments.

  • Package frontend, API, authentication and worker services independently.
  • Run application containers without unnecessary root privileges.
  • Use smaller runtime bases where appropriate instead of full operating-system images.
  • Keep service runtime configuration outside the Dockerfile.
  • Publish traceable images to a private registry instead of relying on floating local builds.
  • Promote one immutable release identity into Kubernetes without rebuilding per environment.

Container Architecture

The application container layer separates each service into its own image and runtime boundary. Supporting data services use PostgreSQL and Redis, while the application images remain independently buildable and deployable.

Frontend
  nginxinc/nginx-unprivileged:alpine
        ↓
API
  python:3.12-slim • UID 10001
        ↓
Auth
  python:3.12-slim • UID 10001
        ↓
Worker
  python:3.12-slim • UID 10001

Supporting services
  PostgreSQL 16 Alpine
  Redis 7 Alpine

For local service composition, Docker Compose defines application dependencies and health checks around PostgreSQL and Redis so dependent services can wait for healthy supporting components.

What I Built

  • Four service-specific Docker build definitions for frontend, API, authentication and worker workloads.
  • An unprivileged NGINX frontend image based on nginxinc/nginx-unprivileged:alpine.
  • Python API, authentication and worker images based on python:3.12-slim.
  • Dedicated non-root application identity using UID 10001 for the Python services.
  • Dependency installation using pip install --no-cache-dir to avoid retaining the package cache in the image layer.
  • Docker Compose service definitions for application dependencies, PostgreSQL and Redis health checks.
  • Jenkins-driven Docker builds for all four application images.
  • Private publication to Amazon ECR with build-specific immutable tags.
  • GitOps release updates so Kubernetes consumes an explicit image version.

Runtime Controls

Non-root Frontend Runtime

The frontend uses the unprivileged NGINX Alpine image rather than a root-oriented default. In Kubernetes the frontend runtime was aligned to port 8080 so the application could serve traffic without requiring privileged port binding.

Dedicated Application User

The API, authentication and worker images create a dedicated system user with UID 10001 and switch to that user before the application process starts. That keeps the normal service process out of the root context.

Lean Runtime Bases

The Python services use python:3.12-slim, while the frontend uses an Alpine-based NGINX image. The goal was to avoid carrying an unnecessarily large general-purpose operating-system image into each service.

Configuration Separation

The Dockerfiles define the application runtime but do not embed production credentials. Environment-specific configuration is supplied by the runtime and deployment layers rather than being compiled into the image definition.

Image Delivery

Jenkins built each application image independently and attached the same build-specific release identifier to the four artifacts. For the validated release, that identifier was build-14.

Jenkins build #14

frontend  → build-14
api       → build-14
auth      → build-14
worker    → build-14
      ↓
Amazon ECR
      ↓
GitOps desired state
      ↓
Argo CD
      ↓
Amazon EKS

This makes the image version explicit at every delivery stage. The workflow does not depend on a floating latest tag to identify what should run in Kubernetes.

Engineering Decisions

One Image Per Service Instead of One Monolithic Runtime

The frontend, API, authentication and worker components have different processes and scaling characteristics. Separate images keep service ownership explicit and allow each workload to be built, released and operated independently.

Root Convenience Versus Runtime Security

Running containers as root can simplify defaults but increases the impact of a container compromise. I used an unprivileged NGINX base for the frontend and a dedicated UID for the Python services, then aligned the Kubernetes runtime configuration with those choices.

Mutable Tags Versus Release Traceability

A floating image reference would make it harder to determine which artifact moved through development, staging and the production-style state. Build-number tags kept one release identity visible across Jenkins, ECR, GitOps and Kubernetes.

Container Success Versus Application Readiness

A successful image build proves that the container can be created, not that the distributed application works end to end. I validated the wider application path through Kubernetes, including service health, ingress routing and worker processing.

Verified Evidence

The validated release connected container engineering to the existing delivery evidence rather than treating Docker as an isolated skill.

FRONTENDUnprivileged NGINXAlpine-based runtime
PYTHONUID 10001API • Auth • Worker
ARTIFACTS4 build-14 ImagesImmutable release set
DELIVERYECR → GitOps → EKSTraceable runtime path

Jenkins build #14 produced the four application images and published them to Amazon ECR. The same release identity was then referenced by the GitOps promotion workflow used for the Kubernetes environments.

Production Considerations

The validated implementation demonstrates service isolation, non-root runtime controls, immutable tagging and private registry delivery. For a larger production organization I would extend the container supply chain with:

  • Container image vulnerability scanning as a required CI gate.
  • Software bill of materials generation for each release.
  • Image signing and admission-time signature verification.
  • Digest-pinned base images for stronger build reproducibility.
  • Multi-stage builds where build-time toolchains can be removed from final runtime images.
  • Centralized base-image lifecycle management and automated patch rebuilds.
  • Registry retention and lifecycle policies around old release artifacts.

Source Code & Evidence

The container implementation is maintained in the application repository alongside the service source and Jenkins pipeline so the build definitions remain reviewable with the code they package.

  • Application & Container Repository — frontend, API, authentication and worker Dockerfiles, Docker Compose configuration and Jenkins delivery pipeline.
  • GitOps Repository — version-controlled Kubernetes desired state consuming immutable application image versions.

Container Release Trace

Application source
      ↓
4 service Dockerfiles
      ↓
Jenkins build #14
      ↓
4 immutable images
      ↓
Amazon ECR
      ↓
GitOps release commits
      ↓
Argo CD
      ↓
Amazon EKS

Engineering Value

This project demonstrates Docker as an engineering control point rather than a logo in a skills list. The container layer defines how each service is packaged, which user runs it, how the release is identified and how the artifact moves into the runtime platform.

It also shows how container engineering connects to the rest of the delivery system: Jenkins produces the images, Amazon ECR stores them, Git records the intended version, Argo CD reconciles the release and Kubernetes operates the workloads. That creates a traceable path from source code to running container.