You can deploy the same Helm chart to multiple environments by using different configuration values for each environment.
Let’s see this in action. Imagine we have a simple web application chart, my-webapp, and we want to deploy it to staging and production environments.
First, we need to create value files for each environment.
values-staging.yaml:
replicaCount: 1
image:
tag: "v1.2.0"
ingress:
enabled: true
host: staging.example.com
values-production.yaml:
replicaCount: 3
image:
tag: "v1.2.0"
ingress:
enabled: true
host: example.com
Now, we can deploy the chart to each environment using these value files.
For staging:
helm upgrade --install my-webapp-staging ./my-webapp \
--namespace staging \
--values values-staging.yaml
For production:
helm upgrade --install my-webapp-production ./my-webapp \
--namespace production \
--values values-production.yaml
Notice how we use different release names (my-webapp-staging, my-webapp-production) and namespaces (staging, production) to logically separate the deployments, even though they originate from the same chart. The image.tag is the same here, but it could also be different per environment if needed.
The core idea is that Helm charts are templates. They define the structure of your deployment, but the specific details are filled in by the values you provide. By supplying different values.yaml files (or inline --set arguments), you customize the deployment for each environment’s unique needs, like the number of replicas, domain names, resource limits, or even entirely different configurations for services and databases.
When Helm renders a chart, it merges the default values.yaml within the chart with any values files you provide and any --set arguments. The order of precedence is typically: --set > provided values files > default values.yaml. This allows for a layered approach to configuration, where you can have base defaults in the chart and override specific settings for staging, then override them again for production.
One of the most powerful aspects of this approach is maintaining consistency while allowing for divergence. You can ensure that the same code is deployed across environments, reducing the "it worked on my machine" problem. The differences are then confined to configuration, which is precisely where environment-specific variations should live. This makes troubleshooting easier, as you can often rule out application code issues by observing that the same chart version behaves differently due to configuration.
The flexibility extends beyond simple overrides. You can conditionally enable or disable resources within your chart using {{ if .Values.someFeature.enabled }} blocks, controlled by values that differ per environment. For example, you might enable detailed logging only in staging or disable an ingress for a local development deployment. This allows a single chart to cater to a wide range of deployment scenarios without becoming overly complex.
The next challenge you’ll likely face is managing these environment-specific configurations at scale, especially as the number of environments and the complexity of your applications grow.