Add Ssl To Docker Container

Add Ssl To Docker Container

Reading time1 min
#Security#DevOps#Containers#Docker#SSL#Nginx

Add SSL to Docker Containers: Practical Implementation and Trade-Offs

Most development teams ignore SSL until production, leading to integration failures, missed HSTS headers, or broken service-to-service encryption. Address SSL from the outset. Here’s a concrete method for embedding SSL directly in a Dockerized Nginx workflow—one that matches production topologies, not just local hacks.


Why SSL Matters in Containerized Environments

Containerized microservices shift network boundaries. When traffic crosses networks—service meshes, ingress controllers, or public endpoints—SSL/TLS is non-negotiable. Packet sniffers can trivially intercept plaintext. GDPR, HIPAA, and most NIST frameworks require encrypted connections. Embedding SSL in the container guarantees that even lateral (east-west) traffic in your cluster remains protected.


Requirements

  • Familiarity with Dockerfile and bind mounts
  • OpenSSL (tested with OpenSSL 1.1.x; newer versions may output warnings)
  • An example Nginx site (v1.25.2 or compatible)
  • Bash shell for scripting (macOS/Linux), or PowerShell equivalents

Step 1: Generate a Self-Signed Certificate

For dev or CI pipelines, self-signed certs suffice. Production should use ACME clients (e.g., Certbot for Let's Encrypt).
Below, generate a 2048-bit RSA cert valid for 365 days with inline subject data—skipping x509 prompts avoids automation failures:

openssl req -x509 -nodes -days 365 \
  -newkey rsa:2048 \
  -keyout certs/server.key \
  -out certs/server.crt \
  -subj "/C=US/ST=State/L=City/O=ExampleOrg/OU=Dev/CN=localhost"

Note: Place server.crt and server.key in a versioned subdirectory, e.g., ./certs. Avoid checking private keys into VCS.


Nginx SSL Configuration Example

Minimal config to serve / via HTTPS:

server {
  listen 443 ssl;
  server_name localhost;  # Replace with service FQDN in production

  ssl_certificate     /etc/nginx/ssl/server.crt;
  ssl_certificate_key /etc/nginx/ssl/server.key;

  ssl_protocols TLSv1.2 TLSv1.3;   # Disable legacy ciphers, as per Mozilla recommendations
  ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';

  location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
  }
}

Gotcha: If you see nginx: [emerg] cannot load certificate key, check permissions and ensure key format is PEM.


Dockerfile Example: Embedding SSL

Here’s a Dockerfile targeting reproducible SSL integration:

FROM nginx:1.25.2-alpine

COPY default.conf /etc/nginx/conf.d/default.conf
COPY certs/server.crt /etc/nginx/ssl/server.crt
COPY certs/server.key /etc/nginx/ssl/server.key

# Optionally copy static app code
# COPY ./html /usr/share/nginx/html

EXPOSE 443

CMD ["nginx", "-g", "daemon off;"]
  • Pin the base image (nginx:1.25.2-alpine) to prevent silent breaking changes.
  • Copy certificates as build assets—avoid this in production unless the image is short-lived.
  • No port 80 exposure; this container only serves HTTPS.

Trade-Off: Baking private keys into the image is insecure for long-lived or multi-tenant images. Mount keys via Docker volumes in production.


Directory Layout

docker-ssl-example/
├── certs/
│   ├── server.crt
│   └── server.key
├── default.conf
└── Dockerfile

Build and Run the Container

Build locally:

docker build -t nginx-ssl-example .

Run with explicit port mapping:

docker run -d --rm \
  -p 443:443 \
  --name nginx-secure \
  nginx-ssl-example

Expected log output (docker logs nginx-secure):

nginx ... [notice] start worker process ...

If you see “nginx: [emerg] host not found”, check your server_name clause.


Verifying SSL Endpoint

Typical browser users get a warning for self-signed certs. For CLI validation, use:

curl -vk https://localhost/

Look for:

* SSL certificate problem: self signed certificate
* Server certificate:
*  subject: C=US; ST=State; ...; CN=localhost
*  start date: ...
*  expire date: ...

Use -k in CI to ignore verification during early tests.


Advanced: Volumes for Certificate Rotation

For containerized production, prefer mounting certificates over embedding. This allows hot-reload without re-imaging after renewal:

version: '3.7'
services:
  web:
    image: nginx:1.25.2-alpine
    ports:
      - "443:443"
    volumes:
      - ./certs:/etc/nginx/ssl:ro
      - ./default.conf:/etc/nginx/conf.d/default.conf:ro

On renewal (say, via a sidecar or Kubernetes secret), Nginx can pick up new certs on reload (docker exec ... nginx -s reload).


Summary

Bake SSL in from the first commit—avoid production drift, unexpected merge conflicts, and late-stage compliance Failures. Use self-signed certs to mirror prod topology in dev, but always swap with ACME-issued certs or Vault-sourced keys before launch.
Practical tip: In multi-env CI, use environment variable interpolations to avoid hardcoding certificate paths.

Known Issue

Nginx in Docker doesn’t support dynamic certificate reload (SIGHUP) for all config changes prior to v1.23. If you expect runtime certificate renewal, confirm image version and test reload semantics.


Further reading: For application-layer SSL (Node.js, Flask, etc.), integration differs—reach out or reference container-specific guides.