Securing Docker Images in Production
Cover photo by Zaqy Al Fattah on Unsplash
Your Docker images are almost certainly leaking sensitive data or running unnecessary services. I see this constantly. Developers focus on the build process and forget that an image is just a snapshot of a file system waiting to be exploited.
Don’t run as root
By default, your container processes run as the root user. That is a massive security failure. If someone breaks out of your application code, they get root access to the container. Always create a dedicated user.
RUN addgroup -S appgroup && adduser -S appuser -G appgroupUSER appuserIt is simple. Do it now.
Use minimal base images
Stop using full Ubuntu or Debian images. They come with hundreds of packages you don’t need (like shell utilities or package managers) that increase your attack surface. Use Alpine or Distroless images instead. They are tiny and secure by design.
Secrets are not for the Dockerfile
I have seen too many people bake API keys or private keys into their images. Even if you multi-stage your build, history layers might still hold that data. Never use ENV or COPY for secrets. Use Docker Secrets or inject them at runtime via environment variables from your orchestration tool.
Keep your dependencies clean
“But wait, I need a shell to debug my container in production!” I hear this all the time. That is a fair concern for local development, but in production, if your container is broken, you should be looking at logs or metrics, not manually poking around a shell. If you really need to debug, use kubectl debug or an ephemeral container pattern instead.
Multi-stage builds are non-negotiable
This is how you keep your production images slim. Separate your build environment from your runtime environment. Your production image should only contain the binary or compiled code.
# Build stageFROM node:20 AS builderWORKDIR /appCOPY package*.json ./RUN npm installCOPY . .RUN npm run build
# Production stageFROM node:20-alpineWORKDIR /appCOPY --from=builder /app/dist ./distCMD ["node", "dist/main.js"]I admit, setting this up feels like extra work on a Friday afternoon. If you are building a small internal tool that never touches the internet, maybe you can relax these rules a bit. Still, why risk it? It takes ten minutes to configure a secure build process.
Scan your images
Don’t just trust that your base image is secure. Use tools like Trivy or Grype to scan your images for vulnerabilities during your CI/CD pipeline. These tools will warn you about outdated packages or known exploits. Automation is your friend here.
Security isn’t a one-time project. It’s a habit. Are you auditing your dependencies every month, or are you just letting them rot in your Dockerfile until they break?