Containers promised us portability, consistency, and scalable deployments. But many development teams are still stuck using them like lightweight VMs — crafting images by hand, running docker exec
, and treating the container itself as a mutable environment.
This isn’t just inefficient. It’s dangerous.
If you care about security, reliability, and scalability, it’s time to rethink your container lifecycle. Here's how to do it right — from build to runtime.
1. The Problem with Manual Container Workflows
You’ve probably seen (or written) something like this:
docker run -it base-image bash
# Install some tools, tweak configs
exit
docker commit ...
docker save -o myimage.tar
This “build” process might work once — but it’s:
- Opaque: Nobody knows what’s inside that image.
- Irreproducible: You can’t rebuild the exact same image.
- Insecure: Unscanned, unsigned, unverifiable.
- Unscalable: CI/CD pipelines can't run you debugging inside a shell.
Manual builds destroy the auditability and automation that containers are supposed to give you. Worse, they make it nearly impossible to maintain a clean supply chain.
2. Why Dockerfiles (and Multi-stage Builds) Matter
A Dockerfile is more than a convenience — it’s your source of truth.
Benefits of Dockerfile-driven builds:
- 100% declarative: Every step is visible and version-controlled
- Portable: Runs the same in CI, your laptop, or a Kubernetes cluster
- Automatable: Can be rebuilt and scanned automatically
- Secureable: Works with image signing, SBOM generation, and policy enforcement
Want to go further? Use multi-stage builds to separate build tools from runtime dependencies — reducing image size and attack surface.
# Build stage
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# Runtime stage
FROM alpine
COPY --from=builder /app/myapp /usr/bin/myapp
CMD ["myapp"]
This pattern results in much smaller, leaner containers — which improves startup time, security posture, and efficiency. As highlighted in Container Size Matters, reducing image bloat isn’t just aesthetic — it has real operational and security benefits.
3. Automate Everything: CI/CD as Your Factory Line
Your container builds should run without human hands involved:
- ✅ Triggered by source commits or tags
- ✅ Build from Dockerfile
- ✅ Scan for vulnerabilities
- ✅ Generate SBOMs
- ✅ Sign images (e.g., with Sigstore)
- ✅ Push to a trusted registry
This builds a secure, traceable software supply chain — not just an image blob.
If you’re using docker save
, docker load
, or dragging tarballs around, you’re not ready for scale.
4. Exec Into Containers? That’s a Red Flag
“Just SSH in and fix it” doesn’t belong in container-native thinking.
Running docker exec
to fix a container is:
- A band-aid, not a solution
- A breach of immutability
- A sign your build process is broken
If you need to exec into a container to troubleshoot, fine — but never use that session as part of your build. Fix the Dockerfile or CI pipeline instead.
5. Stateless by Design: Where Does Your State Live?
Containers should be:
- 🔁 Restartable
- 📦 Replaceable
- 🚀 Scalable
But if you're writing logs, cache, or app state inside /var/lib/myapp
, you’re asking for trouble.
Best practice:
- Store logs in external logging systems (e.g., Fluent Bit → ELK or Loki)
- Store persistent data in mounted volumes
- Use Kubernetes
PersistentVolumeClaims
or cloud-native block storage
This way, your containers can be destroyed and recreated at will, without loss of data or downtime.
6. Putting It All Together
A modern container pipeline looks like this:
- ✅ Git push triggers automated build
- 🐳 Dockerfile builds image using CI
- 🔍 Image scanned and signed
- 📦 Stored in trusted registry
- 🚀 Deployed via declarative manifest (K8s, Helm, etc.)
- 💾 Volumes handle persistence, not the container
No manual tweaks. No snowflake images. Full lifecycle awareness.
Conclusion: Treat Containers Like Code, Not Pets
Containers are a critical part of your software supply chain. If you’re still crafting them by hand or baking in state, you’re setting yourself up for security risks, outages, and scaling pain.
Move to:
- 📄 Declarative Dockerfiles
- 🤖 Automated CI builds
- 🔐 Secure, traceable image delivery
- 🗃️ Clean separation of state and compute
This is how you go from “it works on my machine” to “we ship production-ready containers every commit.”
No comments:
Post a Comment