In many companies, the application runs on a server that someone set up by hand years ago. A release means copying files or running commands over SSH, the developers' machines differ from the server, and nobody likes to touch the runtime version. Containers and DevOps practices change this: the application is built, tested and deployed the same way every time. This article describes the order that works, the mistakes that cost the most, and when Kubernetes is worth it.
What containers change
A container image packages the application with everything it needs to run: the runtime, the libraries, the configuration files (Docker documentation). The same image runs on a developer's laptop, in testing and in production, so "it works on my machine" stops being an argument.
- Upgrades become routine. A new runtime version is a change in one line of the Dockerfile, a rebuild and a test run. The PHP 8.2 end-of-life article shows this path for PHP applications.
- No lock-in. Images follow the open standards of the Open Container Initiative, so they run on any platform that supports them, on your own servers or in the cloud (OCI).
What DevOps means in practice
DevOps is not a tool or a job title but a way of working: everything in version control, every change built and tested automatically, releases that are small, frequent and repeatable, infrastructure described as code, and a team that sees what happens in production.
The order that works
- Take stock. Which applications run where, on which versions, who releases them and how. Most of the risk shows up here: unsupported versions, and manual steps that only one person knows.
- Containerise one application first. A Dockerfile based on a trusted, minimal base image with a pinned version, a multi-stage build that keeps build tools out of the final image, and a non-root user (Docker build best practices). Configuration stays outside the image, in the environment (The Twelve-Factor App).
- A development environment in one command. Docker Compose describes the application with its database, cache and other services in one file and starts the whole stack with one command (Docker documentation). A new team member clones the repository and has a working environment.
- A pipeline for every change. On every commit, the CI system builds the image, runs the tests, scans the image for known vulnerabilities and pushes it to a registry (Docker documentation: GitHub Actions). Registries and Docker's own tools include such scanners, for example Amazon ECR image scanning or Docker Scout.
- The same image in every environment. Testing and production run the same image with different configuration; development, staging and production stay as similar as possible (The Twelve-Factor App). The release is a pipeline step, not a manual task.
- Infrastructure as code and monitoring. Servers, networks, databases and DNS are described in code, for example with Terraform, and changed through the same review as the application. Applications write their logs to standard output, where the platform collects them (The Twelve-Factor App); metrics and alerts show problems before the users do.
Where to run the containers
Not every company needs Kubernetes.
- A few applications, a small team: a server with Docker Compose, or a managed container service where the provider runs the servers, for example Amazon ECS with AWS Fargate.
- Many services, several teams, special requirements for scaling, networking or security: Kubernetes, preferably as a managed cluster such as Amazon EKS. Kubernetes is a platform for running containerised workloads, not a complete, ready-made platform (Kubernetes documentation): upgrades, add-ons, access control and monitoring remain work for your team or for a partner.
The mistakes that cost the most
- Secrets in the image. Build arguments and environment variables set in the Dockerfile stay in the final image and in every copy of it; secrets belong in secret mounts at build time and in a secret store at run time (Docker documentation).
- Floating versions. Tags such as
latestcan point to a different image tomorrow. Pin versions, and for base images consider the digest, which always refers to the same image (Docker build best practices). The same applies to the components of the pipeline: in March 2026, attackers moved almost all version tags of the GitHub Action of Trivy, a widely used vulnerability scanner, to code that steals credentials (Aqua Security advisory). GitHub recommends locking workflow dependencies with a lockfile or a full-length commit SHA (GitHub documentation). - Images built once and never again. An image does not update itself. Rebuild regularly, so the security fixes of the base image arrive (same page).
- Data inside the container. Files written inside a container are lost when the container is destroyed, for example when a new release replaces it; databases and uploaded files belong in volumes or managed services (Docker documentation).
- Containers treated like servers. Logging in and changing things by hand defeats the purpose: the change disappears with the next release, and nobody knows it existed. Changes go through the image and the pipeline.
- Kubernetes too early. A cluster for two applications brings more work than it saves.
An example
At Talk-A-Bot, the platform and 86+ client projects ran on three clouds, each with its own setup and manual deployment steps. With that many services, Kubernetes was the right platform: everything was consolidated onto one AWS platform on Amazon EKS, with the infrastructure in Terraform, standardised Helm charts and GitOps deployments. Infrastructure costs fell by 77%. The details are in the Talk-A-Bot case study.
Where I can help
I introduce containers and DevOps practices in companies: an assessment of what runs where, containerisation, a reproducible development environment, CI/CD pipelines, infrastructure as code, and training, so your team can run the setup on its own. On request, I also host and operate the containerised applications. Changes to the application code stay with your developers.