Repository: shipwright-io/build
Pull request #817 (2021-06-22 20:22:32)

SHIP-0004: Build Environment Variables
# Changes

<!-- 🎉🎉🎉 Thank you for the PR!!! 🎉🎉🎉 -->

This pull request will give developers the abilty to add environment variables to build strategy steps. Conversely, this proposal will also give build strategy authors the ability to fix the values of environment variables in build steps or provide non-empty default values.

# Submitter Checklist

- [x] Includes tests if functionality changed/was added
- [x] Includes docs if changes are user-facing
- [x] [Set a kind label on this PR](https://prow.k8s.io/command-help#kind)  
- [x] Release notes block has been filled in, or marked NONE

See [the contributor guide](https://github.com/shipwright-io/build/blob/main/CONTRIBUTING.md)
for details on coding conventions, github and prow interactions, and the code review process.

# Release Notes

```release-note
Developers can now add Environment Variables to the Spec section of their Builds and BuildRuns.
The Environment Variables will be added to all Steps of the chosen BuildStrategy, though any conflicts
will result in a failed TaskRun.
```


openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **qu1queee** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @qu1queee` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=shipwright-io%2Fbuild).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/shipwright-io/build/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["qu1queee"]} -->
openshift-ci[bot]: @coreydaley: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
qu1queee: @coreydaley can we close this one? see https://github.com/shipwright-io/community/pull/10 ( is already merged )
coreydaley: > @coreydaley can we close this one? see [shipwright-io/community#10](https://github.com/shipwright-io/community/pull/10) ( is already merged )

This is the implementation for https://github.com/shipwright-io/community/pull/10 ...
qu1queee: @coreydaley my bad, thanks for the clarification 
openshift-ci[bot]: @coreydaley: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
coreydaley: /retest
coreydaley: /assign @SaschaSchwarze0 
openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please ask for approval from **adambkaplan** after the PR has been reviewed.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=shipwright-io%2Fbuild).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/shipwright-io/build/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["adambkaplan"]} -->
coreydaley: > Generally looks good, but given this is a substantial feature this PR needs:
> 
>     1. Doc updates for our Build and BuildRun APIs.
> 
>     2. A release note block in the PR description.

Working on the `docs` update already.
coreydaley: Documentation update: https://github.com/shipwright-io/website/pull/53
coreydaley: Looks like a networking issue:
```
error creating build container: Error reading blob sha256:e571554b15ee6f4301b3e1cf58735a1504380734dade095784e14197eea233f8: Get "https://cdn02.quay.io/sha256/e5/e571554b15ee6f4301b3e1cf58735a1504380734dade095784e14197eea233f8?Expires=1632847588&Signature=OG~knNhsUflW2XAC3MjGAicDdiEKMxMWlB4vIjF4JkI3WBp5O7sp2ffRULJHvK1H~gBzH7wH4mQe~5rRKlwhGgSNCOT1PZ-Edc3~oBCVMbwNEi2UlI~6gawEvxgcGzGN9Uj6iDj~dE1jjAzqPVL~2zh1eIjju2qUsu-~~vxzOpRoPvI9gTd8F7ynj0fj-ZFbUmrt8lKmooOjUcb82O5KQ~lQ4PitDoPtsRNt0RJx96veZG~jVHcuMfACRRZQJeYqbZawLjC4voGHBWF0DbIutUIgX6~zk1GNzviBHPpwfJi9SaeBHD6WLAe8VIgZeHNGczWW0YYywA925yUqPIsVeQ__&Key-Pair-Id=APKAJ67PQLWGCSP66DGA": net/http: TLS handshake timeout
```

@adambkaplan Can I get a retest?
adambkaplan: @coreydaley for future reference I think you can mention @shipwright-io/build-reviewers, and anyone on that team will be able to rerun any GitHub action by clicking on the details.

You may be also able to re-run the GitHub action as the PR author since technically the tests run against your fork.
coreydaley: > @coreydaley for future reference I think you can mention @shipwright-io/build-reviewers, and anyone on that team will be able to rerun any GitHub action by clicking on the details.
> 
> You may be also able to re-run the GitHub action as the PR author since technically the tests run against your fork.

I will keep that in mind. I was not able to rerun the tests.
coreydaley: I don't think that adding additional documentation to support valueFrom with the EnvVars would be an issue, and it should flow through to tekton seamlessly. Additional supporting tests should not be an issue either. 
However, the shipwright/cli would not be able to support adding environment variables utilizing valueFrom to Builds or BuildRuns, I also don't think that OpenShift currently supports that through the oc cli either.
gabemontero: > I don't think that adding additional documentation to support valueFrom with the EnvVars would be an issue, and it should flow through to tekton seamlessly. Additional supporting tests should not be an issue either. However, the shipwright/cli would not be able to support adding environment variables utilizing valueFrom to Builds or BuildRuns, I also don't think that OpenShift currently supports that through the oc cli either.

the meets min for me would be doc and tests that address the various valueFrom paths with k8s env vars ... if those are added, and we are all good with supporting those scenarios, I'm good

integration into `shp` i.e. cli is a stretch item that could either never be addressed or addressed at a later point based on customer feedback, though the fact that openshift has k8s corev1 env vars for 7 years now and nobody has asked for cli enablement there is telling IMO

so yeah @coreydaley pending any comeback from @adambkaplan about gettnig simpler here wrt env vars, start adding the additional env var permutations and we'll go from there
coreydaley: FYI, I am still working on the valueFrom stuff that was mentioned by @gabemontero during his review, and have not pushed those updates as of yet.
SaschaSchwarze0: > FYI, I am still working on the valueFrom stuff that was mentioned by @gabemontero during his review, and have not pushed those updates as of yet.

Thanks, was assuming something like this. You may prefix your PR title with `WIP` in such cases.
gabemontero: > a couple of bigger macro items @coreydaley @adambkaplan that came to my mind, including perhaps missing a key element of type during the SHIP review

ok my bigger macro items have been addressed ... I will thus 

/approve

given the collaboration I see in the commits between @coreydaley @HeavyWombat @shahulsonhal, as well as the review from @SaschaSchwarze0 I am going to defer the lgtm to one of them.  Aside from their in depth involvement, it allows for the cross team etc. sign off we prefer.

thanks everyone
openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/shipwright-io/build/pull/817#issuecomment-934610222" title="Approved">gabemontero</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=shipwright-io%2Fbuild).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/shipwright-io/build/blob/main/OWNERS)~~ [gabemontero]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/cmd/git/main.go b/cmd/git/main.go
index 89628dcb..09a31138 100644
--- a/cmd/git/main.go
+++ b/cmd/git/main.go
@@ -27,8 +27,6 @@ const (
 	typeUsernamePassword
 )
 
-var useNoTagsFlag = false
-
 // ExitError is an error which has an exit code to be used in os.Exit() to
 // return both an exit code and an error message
 type ExitError struct {
@@ -111,10 +109,6 @@ func Execute(ctx context.Context) error {
 		return err
 	}
 
-	// Check if Git CLI supports --no-tags for clone
-	out, _ := git(ctx, "clone", "-h")
-	useNoTagsFlag = strings.Contains(out, "--no-tags")
-
 	if err := runGitClone(ctx); err != nil {
 		return err
 	}
@@ -196,10 +190,7 @@ func clone(ctx context.Context) error {
 	cloneArgs := []string{
 		"clone",
 		"--quiet",
-	}
-
-	if useNoTagsFlag {
-		cloneArgs = append(cloneArgs, "--no-tags")
+		"--no-tags",
 	}
 
 	var commitSha string
diff --git a/deploy/crds/shipwright.io_buildruns.yaml b/deploy/crds/shipwright.io_buildruns.yaml
index c6c9664c..91fc20ce 100644
--- a/deploy/crds/shipwright.io_buildruns.yaml
+++ b/deploy/crds/shipwright.io_buildruns.yaml
@@ -64,6 +64,86 @@ spec:
                 required:
                 - name
                 type: object
+              env:
+                description: Env contains additional environment variables that should be passed to the build container
+                items:
+                  description: EnvVar represents an environment variable present in a Container.
+                  properties:
+                    name:
+                      description: Name of the environment variable. Must be a C_IDENTIFIER.
+                      type: string
+                    value:
+                      description: 'Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".'
+                      type: string
+                    valueFrom:
+                      description: Source for the environment variable's value. Cannot be used if value is not empty.
+                      properties:
+                        configMapKeyRef:
+                          description: Selects a key of a ConfigMap.
+                          properties:
+                            key:
+                              description: The key to select.
+                              type: string
+                            name:
+                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
+                              type: string
+                            optional:
+                              description: Specify whether the ConfigMap or its key must be defined
+                              type: boolean
+                          required:
+                          - key
+                          type: object
+                        fieldRef:
+                          description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels[''<KEY>'']`, `metadata.annotations[''<KEY>'']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.'
+                          properties:
+                            apiVersion:
+                              description: Version of the schema the FieldPath is written in terms of, defaults to "v1".
+                              type: string
+                            fieldPath:
+                              description: Path of the field to select in the specified API version.
+                              type: string
+                          required:
+                          - fieldPath
+                          type: object
+                        resourceFieldRef:
+                          description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.'
+                          properties:
+                            containerName:
+                              description: 'Container name: required for volumes, optional for env vars'
+                              type: string
+                            divisor:
+                              anyOf:
+                              - type: integer
+                              - type: string
+                              description: Specifies the output format of the exposed resources, defaults to "1"
+                              pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                              x-kubernetes-int-or-string: true
+                            resource:
+                              description: 'Required: resource to select'
+                              type: string
+                          required:
+                          - resource
+                          type: object
+                        secretKeyRef:
+                          description: Selects a key of a secret in the pod's namespace
+                          properties:
+                            key:
+                              description: The key of the secret to select from.  Must be a valid secret key.
+                              type: string
+                            name:
+                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
+                              type: string
+                            optional:
+                              description: Specify whether the Secret or its key must be defined
+                              type: boolean
+                          required:
+                          - key
+                          type: object
+                      type: object
+                  required:
+                  - name
+                  type: object
+                type: array
               output:
                 description: Output refers to the location where the generated image would be pushed to. It will overwrite the output image in build spec
                 properties:
@@ -159,6 +239,86 @@ spec:
                   dockerfile:
                     description: Dockerfile is the path to the Dockerfile to be used for build strategies which bank on the Dockerfile for building an image.
                     type: string
+                  env:
+                    description: Env contains additional environment variables that should be passed to the build container
+                    items:
+                      description: EnvVar represents an environment variable present in a Container.
+                      properties:
+                        name:
+                          description: Name of the environment variable. Must be a C_IDENTIFIER.
+                          type: string
+                        value:
+                          description: 'Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".'
+                          type: string
+                        valueFrom:
+                          description: Source for the environment variable's value. Cannot be used if value is not empty.
+                          properties:
+                            configMapKeyRef:
+                              description: Selects a key of a ConfigMap.
+                              properties:
+                                key:
+                                  description: The key to select.
+                                  type: string
+                                name:
+                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
+                                  type: string
+                                optional:
+                                  description: Specify whether the ConfigMap or its key must be defined
+                                  type: boolean
+                              required:
+                              - key
+                              type: object
+                            fieldRef:
+                              description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels[''<KEY>'']`, `metadata.annotations[''<KEY>'']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.'
+                              properties:
+                                apiVersion:
+                                  description: Version of the schema the FieldPath is written in terms of, defaults to "v1".
+                                  type: string
+                                fieldPath:
+                                  description: Path of the field to select in the specified API version.
+                                  type: string
+                              required:
+                              - fieldPath
+                              type: object
+                            resourceFieldRef:
+                              description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.'
+                              properties:
+                                containerName:
+                                  description: 'Container name: required for volumes, optional for env vars'
+                                  type: string
+                                divisor:
+                                  anyOf:
+                                  - type: integer
+                                  - type: string
+                                  description: Specifies the output format of the exposed resources, defaults to "1"
+                                  pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                                  x-kubernetes-int-or-string: true
+                                resource:
+                                  description: 'Required: resource to select'
+                                  type: string
+                              required:
+                              - resource
+                              type: object
+                            secretKeyRef:
+                              description: Selects a key of a secret in the pod's namespace
+                              properties:
+                                key:
+                                  description: The key of the secret to select from.  Must be a valid secret key.
+                                  type: string
+                                name:
+                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
+                                  type: string
+                                optional:
+                                  description: Specify whether the Secret or its key must be defined
+                                  type: boolean
+                              required:
+                              - key
+                              type: object
+                          type: object
+                      required:
+                      - name
+                      type: object
+                    type: array
                   output:
                     description: Output refers to the location where the built image would be pushed.
                     properties:
diff --git a/deploy/crds/shipwright.io_builds.yaml b/deploy/crds/shipwright.io_builds.yaml
index 765fa985..11fa29b6 100644
--- a/deploy/crds/shipwright.io_builds.yaml
+++ b/deploy/crds/shipwright.io_builds.yaml
@@ -82,6 +82,86 @@ spec:
               dockerfile:
                 description: Dockerfile is the path to the Dockerfile to be used for build strategies which bank on the Dockerfile for building an image.
                 type: string
+              env:
+                description: Env contains additional environment variables that should be passed to the build container
+                items:
+                  description: EnvVar represents an environment variable present in a Container.
+                  properties:
+                    name:
+                      description: Name of the environment variable. Must be a C_IDENTIFIER.
+                      type: string
+                    value:
+                      description: 'Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".'
+                      type: string
+                    valueFrom:
+                      description: Source for the environment variable's value. Cannot be used if value is not empty.
+                      properties:
+                        configMapKeyRef:
+                          description: Selects a key of a ConfigMap.
+                          properties:
+                            key:
+                              description: The key to select.
+                              type: string
+                            name:
+                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
+                              type: string
+                            optional:
+                              description: Specify whether the ConfigMap or its key must be defined
+                              type: boolean
+                          required:
+                          - key
+                          type: object
+                        fieldRef:
+                          description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels[''<KEY>'']`, `metadata.annotations[''<KEY>'']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.'
+                          properties:
+                            apiVersion:
+                              description: Version of the schema the FieldPath is written in terms of, defaults to "v1".
+                              type: string
+                            fieldPath:
+                              description: Path of the field to select in the specified API version.
+                              type: string
+                          required:
+                          - fieldPath
+                          type: object
+                        resourceFieldRef:
+                          description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.'
+                          properties:
+                            containerName:
+                              description: 'Container name: required for volumes, optional for env vars'
+                              type: string
+                            divisor:
+                              anyOf:
+                              - type: integer
+                              - type: string
+                              description: Specifies the output format of the exposed resources, defaults to "1"
+                              pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                              x-kubernetes-int-or-string: true
+                            resource:
+                              description: 'Required: resource to select'
+                              type: string
+                          required:
+                          - resource
+                          type: object
+                        secretKeyRef:
+                          description: Selects a key of a secret in the pod's namespace
+                          properties:
+                            key:
+                              description: The key of the secret to select from.  Must be a valid secret key.
+                              type: string
+                            name:
+                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
+                              type: string
+                            optional:
+                              description: Specify whether the Secret or its key must be defined
+                              type: boolean
+                          required:
+                          - key
+                          type: object
+                      type: object
+                  required:
+                  - name
+                  type: object
+                type: array
               output:
                 description: Output refers to the location where the built image would be pushed.
                 properties:
diff --git a/docs/build.md b/docs/build.md
index 0dcc1b9a..11074837 100644
--- a/docs/build.md
+++ b/docs/build.md
@@ -28,6 +28,7 @@ A `Build` resource allows the user to define:
 - builder
 - dockerfile
 - output
+- env
 
 A `Build` is available within a namespace.
 
@@ -61,6 +62,8 @@ In order to prevent users from triggering `BuildRuns` (_execution of a Build_) t
 | UndefinedParameter | One or many defined `params` are not defined in the referenced strategy. Please ensure that the strategy defines them under its `spec.parameters` list. |
 | RemoteRepositoryUnreachable | The defined `spec.source.url` was not found. This validation only take place for http/https protocols. |
 | BuildNameInvalid | The defined `Build` name (`metadata.name`) is invalid. The `Build` name should be a [valid label value](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set). |
+| SpecEnvNameCanNotBeBlank | Indicates that the name for a user provided environment variable is blank. |
+| SpecEnvValueCanNotBeBlank | Indicates that the value for a user provided environment variable is blank. |
 
 ## Configuring a Build
 
@@ -84,6 +87,7 @@ The `Build` definition supports the following fields:
   - `metadata.annotations[build.shipwright.io/build-run-deletion]` - Defines if delete all related BuildRuns when deleting the Build. The default is `false`.
   - `spec.output.annotations` - Refers to a list of `key/value` that could be used to [annotate](https://github.com/opencontainers/image-spec/blob/main/annotations.md) the output image.
   - `spec.output.labels` - Refers to a list of `key/value` that could be used to label the output image.
+  - `spec.env` - Specifies additional environment variables that should be passed to the build container. The available variables depend on the tool that is being used by the chosen build strategy.
 
 ### Defining the Source
 
@@ -153,6 +157,63 @@ spec:
     revision: v0.1.0
 ```
 
+Example of a `Build` that specifies environment variables:
+
+```yaml
+apiVersion: shipwright.io/v1alpha1
+kind: Build
+metadata:
+  name: buildah-golang-build
+spec:
+  source:
+    url: https://github.com/shipwright-io/sample-go
+    contextDir: docker-build
+  env:
+    - name: EXAMPLE_VAR_1
+      value: "example-value-1"
+    - name: EXAMPLE_VAR_2
+      value: "example-value-2"
+```
+
+Example of a `Build` that uses the Kubernetes Downward API to
+expose a `Pod` field as an environment variable:
+
+```yaml
+apiVersion: shipwright.io/v1alpha1
+kind: Build
+metadata:
+  name: buildah-golang-build
+spec:
+  source:
+    url: https://github.com/shipwright-io/sample-go
+    contextDir: docker-build
+  env:
+    - name: POD_NAME
+      valueFrom:
+        fieldRef:
+          fieldPath: metadata.name
+```
+
+Example of a `Build` that uses the Kubernetes Downward API to
+expose a `Container` field as an environment variable:
+
+```yaml
+apiVersion: shipwright.io/v1alpha1
+kind: Build
+metadata:
+  name: buildah-golang-build
+spec:
+  source:
+    url: https://github.com/shipwright-io/sample-go
+    contextDir: docker-build
+  env:
+    - name: MEMORY_LIMIT
+      valueFrom:
+        resourceFieldRef:
+          containerName: my-container
+          resource: limits.memory
+```
+
 ### Defining the Strategy
 
 A `Build` resource can specify the `BuildStrategy` to use, these are:
diff --git a/docs/buildrun.md b/docs/buildrun.md
index 3387dc6c..f5c9df01 100644
--- a/docs/buildrun.md
+++ b/docs/buildrun.md
@@ -62,6 +62,7 @@ The `BuildRun` definition supports the following fields:
   - `spec.paramValues` - Override any _params_ defined in the referenced `Build`, as long as their name matches.
   - `spec.output.image` - Refers to a custom location where the generated image would be pushed. The value will overwrite the `output.image` value which is defined in `Build`. ( Note: other properties of the output, for example, the credentials cannot be specified in the buildRun spec. )
   - `spec.output.credentials.name` - Reference an existing secret to get access to the container registry. This secret will be added to the service account along with the ones requested by the `Build`.
+  - `spec.env` - Specifies additional environment variables that should be passed to the build container. Overrides any environment variables that are specified in the `Build` resource. The available variables depend on the tool that is being used by the chosen build strategy.
 
 ### Defining the BuildRef
 
@@ -153,6 +154,62 @@ spec:
   state: "BuildRunCanceled"
 ```
 
+### Specifying Environment Variables
+
+An example of a `BuildRun` that specifies environment variables:
+
+```yaml
+apiVersion: shipwright.io/v1alpha1
+kind: BuildRun
+metadata:
+  name: buildpack-nodejs-buildrun-namespaced
+spec:
+  buildRef:
+    name: buildpack-nodejs-build-namespaced
+  env:
+    - name: EXAMPLE_VAR_1
+      value: "example-value-1"
+    - name: EXAMPLE_VAR_2
+      value: "example-value-2"
+```
+
+Example of a `BuildRun` that uses the Kubernetes Downward API to
+expose a `Pod` field as an environment variable:
+
+```yaml
+apiVersion: shipwright.io/v1alpha1
+kind: BuildRun
+metadata:
+  name: buildpack-nodejs-buildrun-namespaced
+spec:
+  buildRef:
+    name: buildpack-nodejs-build-namespaced
+  env:
+    - name: POD_NAME
+      valueFrom:
+        fieldRef:
+          fieldPath: metadata.name
+```
+
+Example of a `BuildRun` that uses the Kubernetes Downward API to
+expose a `Container` field as an environment variable:
+
+```yaml
+apiVersion: shipwright.io/v1alpha1
+kind: BuildRun
+metadata:
+  name: buildpack-nodejs-buildrun-namespaced
+spec:
+  buildRef:
+    name: buildpack-nodejs-build-namespaced
+  env:
+    - name: MEMORY_LIMIT
+      valueFrom:
+        resourceFieldRef:
+          containerName: my-container
+          resource: limits.memory
+```
+
 ## BuildRun Status
 
 The `BuildRun` resource is updated as soon as the current image building status changes:
diff --git a/go.sum b/go.sum
index 77ec589e..67276d70 100644
--- a/go.sum
+++ b/go.sum
@@ -15,7 +15,6 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
 cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
 cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
 cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
-cloud.google.com/go v0.72.0 h1:eWRCuwubtDrCJG0oSUMgnsbD4CmPFQF2ei4OFbXvwww=
 cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
 cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
@@ -41,11 +40,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d h1:LblfooH1lKOpp1hIhukktmSAxFkqMPFk9KR6iZ0MJNI=
 contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY=
-contrib.go.opencensus.io/exporter/prometheus v0.2.1-0.20200609204449-6bcf6f8577f0 h1:2O3c1g5CzMc1+Uah4Waot9Obm0yw70VXJzWaP6Fz3nw=
 contrib.go.opencensus.io/exporter/prometheus v0.2.1-0.20200609204449-6bcf6f8577f0/go.mod h1:MjHoxkI7Ny27toPeFkRbXbzVjzIGkwOAptrAy8Mxtm8=
-contrib.go.opencensus.io/exporter/stackdriver v0.13.5 h1:TNaexHK16gPUoc7uzELKOU7JULqccn1NDuqUxmxSqfo=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
 contrib.go.opencensus.io/exporter/zipkin v0.1.2/go.mod h1:mP5xM3rrgOjpn79MM8fZbj3gsxcuytSqtH0dxSWW1RE=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
@@ -71,7 +67,6 @@ github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxB
 github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
 github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
 github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
-github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=
 github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
 github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc=
 github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA=
@@ -82,7 +77,6 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
 github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
 github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20200415212048-7901bc822317/go.mod h1:DF8FZRxMHMGv/vP2lQP6h+dYzzjpuRn24VeRiYn3qjQ=
@@ -92,7 +86,6 @@ github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcy
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
 github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
-github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
 github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
 github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
 github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
@@ -125,19 +118,15 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx
 github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
 github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
-github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
 github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
 github.com/alecthomas/jsonschema v0.0.0-20180308105923-f2c93856175a/go.mod h1:qpebaTNSsyUn5rPSJMsfqEtDw71TTggXM6stUDI16HA=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
-github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
@@ -146,7 +135,6 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
@@ -156,7 +144,6 @@ github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo
 github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.28.2/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go v1.31.12 h1:SxRRGyhlCagI0DYkhOg+FgdXGXzRTE3vEX/gsgFaiKQ=
 github.com/aws/aws-sdk-go v1.31.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -172,7 +159,6 @@ github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
 github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
 github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
 github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
-github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE=
 github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc=
 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
 github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q=
@@ -185,7 +171,6 @@ github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9cop
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk=
 github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@@ -203,7 +188,6 @@ github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX
 github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
 github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cloudevents/sdk-go/v2 v2.1.0 h1:bmgrU8k+K2ppZ+G/q5xEQx/Xk9HRtJmkrEO3qtDO2k0=
 github.com/cloudevents/sdk-go/v2 v2.1.0/go.mod h1:3CTrpB4+u7Iaj6fd7E2Xvm5IxMdRoaAhqaRVnOr2rCU=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -1717,6 +1701,7 @@ k8s.io/kube-openapi v0.0.0-20210113233702-8566a335510f h1:ZcWbnalfwIst131Zff7dGd
 k8s.io/kube-openapi v0.0.0-20210113233702-8566a335510f/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
 k8s.io/kubectl v0.20.2 h1:mXExF6N4eQUYmlfXJmfWIheCBLF6/n4VnwQKbQki5iE=
 k8s.io/kubectl v0.20.2/go.mod h1:/bchZw5fZWaGZxaRxxfDQKej/aDEtj/Tf9YSS4Jl0es=
+k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
 k8s.io/legacy-cloud-providers v0.19.7/go.mod h1:dsZk4gH9QIwAtHQ8CK0Ps257xlfgoXE3tMkMNhW2xDU=
 k8s.io/metrics v0.20.2/go.mod h1:yTck5nl5wt/lIeLcU6g0b8/AKJf2girwe0PQiaM4Mwk=
 k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
diff --git a/pkg/apis/build/v1alpha1/build_types.go b/pkg/apis/build/v1alpha1/build_types.go
index 22dfb5ae..da1d6d87 100644
--- a/pkg/apis/build/v1alpha1/build_types.go
+++ b/pkg/apis/build/v1alpha1/build_types.go
@@ -30,6 +30,12 @@ const (
 	SpecBuilderSecretRefNotFound BuildReason = "SpecBuilderSecretRefNotFound"
 	// MultipleSecretRefNotFound indicates that multiple secrets are missing
 	MultipleSecretRefNotFound BuildReason = "MultipleSecretRefNotFound"
+	// SpecEnvNameCanNotBeBlank indicates that the name for an environment variable is blank
+	SpecEnvNameCanNotBeBlank BuildReason = "SpecEnvNameCanNotBeBlank"
+	// SpecEnvOnlyOneOfValueOrValueFromMustBeSpecified indicates that both value and valueFrom were specified
+	SpecEnvOnlyOneOfValueOrValueFromMustBeSpecified BuildReason = "SpecEnvOnlyOneOfValueOrValueFromMustBeSpecified"
+	// RuntimePathsCanNotBeEmpty indicates that the spec.runtime feature is used but the paths were not specified
+	RuntimePathsCanNotBeEmpty BuildReason = "RuntimePathsCanNotBeEmpty"
 	// RestrictedParametersInUse indicates the definition of reserved shipwright parameters
 	RestrictedParametersInUse BuildReason = "RestrictedParametersInUse"
 	// UndefinedParameter indicates the definition of param that was not defined in the strategy parameters
@@ -107,6 +113,10 @@ type BuildSpec struct {
 	// +optional
 	// +kubebuilder:validation:Format=duration
 	Timeout *metav1.Duration `json:"timeout,omitempty"`
+
+	// Env contains additional environment variables that should be passed to the build container
+	// +optional
+	Env []corev1.EnvVar `json:"env,omitempty"`
 }
 
 // StrategyName returns the name of the configured strategy, or 'undefined' in
diff --git a/pkg/apis/build/v1alpha1/buildrun_types.go b/pkg/apis/build/v1alpha1/buildrun_types.go
index fb11e31c..efde7353 100644
--- a/pkg/apis/build/v1alpha1/buildrun_types.go
+++ b/pkg/apis/build/v1alpha1/buildrun_types.go
@@ -49,6 +49,10 @@ type BuildRunSpec struct {
 	// State is used for canceling a buildrun (and maybe more later on).
 	// +optional
 	State BuildRunRequestedState `json:"state,omitempty"`
+
+	// Env contains additional environment variables that should be passed to the build container
+	// +optional
+	Env []corev1.EnvVar `json:"env,omitempty"`
 }
 
 // BuildRunRequestedState defines the buildrun state the user can provide to override whatever is the current state.
diff --git a/pkg/apis/build/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/build/v1alpha1/zz_generated.deepcopy.go
index 6c50ed01..3a3c0dcd 100644
--- a/pkg/apis/build/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/build/v1alpha1/zz_generated.deepcopy.go
@@ -180,6 +180,13 @@ func (in *BuildRunSpec) DeepCopyInto(out *BuildRunSpec) {
 		*out = new(Image)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Env != nil {
+		in, out := &in.Env, &out.Env
+		*out = make([]corev1.EnvVar, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
 	return
 }
 
@@ -294,6 +301,13 @@ func (in *BuildSpec) DeepCopyInto(out *BuildSpec) {
 		*out = new(v1.Duration)
 		**out = **in
 	}
+	if in.Env != nil {
+		in, out := &in.Env, &out.Env
+		*out = make([]corev1.EnvVar, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
 	return
 }
 
diff --git a/pkg/env/env.go b/pkg/env/env.go
new file mode 100644
index 00000000..b6b147cc
--- /dev/null
+++ b/pkg/env/env.go
@@ -0,0 +1,57 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package env
+
+import (
+	"fmt"
+
+	corev1 "k8s.io/api/core/v1"
+	kerrors "k8s.io/apimachinery/pkg/util/errors"
+)
+
+// MergeEnvVars merges one slice of corev1.EnvVar into another slice of corev1.EnvVar
+// if overwriteValues is false, this function will return an error if a duplicate EnvVar name is encountered
+// if overwriteValues is true, this function will overwrite the existing value with the new value if a duplicate is encountered
+func MergeEnvVars(new []corev1.EnvVar, into []corev1.EnvVar, overwriteValues bool) ([]corev1.EnvVar, error) {
+	// if new, into, or both are empty, there is no need to run through the processing logic
+	// just quickly return the appropriate value
+	if len(new) == 0 && len(into) == 0 {
+		return []corev1.EnvVar{}, nil
+	} else if len(new) == 0 {
+		return into, nil
+	} else if len(into) == 0 {
+		return new, nil
+	}
+
+	// create a map of the original (into) env vars with the name as the key and
+	// their index as the value so we can do value replacements later if overwriteValues is true
+	originalEnvs := make(map[string]int)
+
+	for i, o := range into {
+		originalEnvs[o.Name] = i
+	}
+
+	// errs holds a slice of error objects from the merge process
+	var errs []error
+
+	// merge the new env vars into the original env vars list following a few simple rules
+	// based on if the name already exists and whether overwriteValues is true or false
+	for _, n := range new {
+		_, exists := originalEnvs[n.Name]
+
+		switch {
+		case exists && overwriteValues:
+			into[originalEnvs[n.Name]] = n
+		case exists && !overwriteValues:
+			errs = append(errs, fmt.Errorf("environment variable %q already exists", n.Name))
+		default:
+			into = append(into, n)
+		}
+	}
+
+	// kerrors.NewAggregate will return nil if the slice is empty
+	// or an aggregated error otherwise
+	return into, kerrors.NewAggregate(errs)
+}
diff --git a/pkg/reconciler/build/build.go b/pkg/reconciler/build/build.go
index 1c8c99f8..180035cc 100644
--- a/pkg/reconciler/build/build.go
+++ b/pkg/reconciler/build/build.go
@@ -73,6 +73,7 @@ func (r *ReconcileBuild) Reconcile(request reconcile.Request) (reconcile.Result,
 		validate.Strategies,
 		validate.Sources,
 		validate.BuildName,
+		validate.Envs,
 	}
 
 	// trigger all current validations
diff --git a/pkg/reconciler/buildrun/resources/taskrun.go b/pkg/reconciler/buildrun/resources/taskrun.go
index 8d5d0c72..cdc081c7 100644
--- a/pkg/reconciler/buildrun/resources/taskrun.go
+++ b/pkg/reconciler/buildrun/resources/taskrun.go
@@ -18,6 +18,7 @@ import (
 
 	buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
 	"github.com/shipwright-io/build/pkg/config"
+	"github.com/shipwright-io/build/pkg/env"
 )
 
 const (
@@ -150,6 +151,13 @@ func GenerateTaskSpec(
 
 	}
 
+	// Combine the environment variables specified in the Build object and the BuildRun object
+	// env vars in the BuildRun supercede those in the Build, overwriting them
+	combinedEnvs, err := env.MergeEnvVars(buildRun.Spec.Env, build.Spec.Env, true)
+	if err != nil {
+		return nil, err
+	}
+
 	// define the steps coming from the build strategy
 	for _, containerValue := range buildSteps {
 
@@ -165,6 +173,13 @@ func GenerateTaskSpec(
 
 		taskImage := getStringTransformations(containerValue.Image)
 
+		// Any collision between the env vars in the Container step and those in the Build/BuildRun
+		// will result in an error and cause a failed TaskRun
+		stepEnv, err := env.MergeEnvVars(combinedEnvs, containerValue.Env, false)
+		if err != nil {
+			return &generatedTaskSpec, fmt.Errorf("error(s) occurred merging environment variables into BuildStrategy %q steps: %s", build.Spec.StrategyName(), err.Error())
+		}
+
 		step := v1beta1.Step{
 			Container: corev1.Container{
 				Image:           taskImage,
@@ -176,7 +191,7 @@ func GenerateTaskSpec(
 				SecurityContext: containerValue.SecurityContext,
 				WorkingDir:      containerValue.WorkingDir,
 				Resources:       containerValue.Resources,
-				Env:             containerValue.Env,
+				Env:             stepEnv,
 			},
 		}
 
diff --git a/pkg/validate/envvars.go b/pkg/validate/envvars.go
new file mode 100644
index 00000000..4bc1d58c
--- /dev/null
+++ b/pkg/validate/envvars.go
@@ -0,0 +1,65 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package validate
+
+import (
+	"context"
+	"fmt"
+
+	corev1 "k8s.io/api/core/v1"
+	kerrors "k8s.io/apimachinery/pkg/util/errors"
+
+	build "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
+)
+
+// Env implements the Env interface to add validations for the `build.spec.env` slice.
+type Env struct {
+	Build *build.Build
+}
+
+// ValidatePath executes the validation routine, inspecting the `build.spec.env` path, which
+// contains a slice of corev1.EnvVar.
+func (e *Env) ValidatePath(_ context.Context) error {
+	if e.Build.Spec.Env == nil {
+		return nil
+	}
+
+	var allErrs []error
+	for _, envVar := range e.Build.Spec.Env {
+		if errs := e.validate(envVar); len(errs) != 0 {
+			allErrs = append(allErrs, errs...)
+		}
+	}
+
+	if len(allErrs) != 0 {
+		return fmt.Errorf("%s", kerrors.NewAggregate(allErrs).Error())
+	}
+
+	return nil
+}
+
+// validate inspects each environment variable and validates all required attributes.
+func (e *Env) validate(envVar corev1.EnvVar) []error {
+	var allErrs []error
+
+	if envVar.Name == "" {
+		e.Build.Status.Reason = build.SpecEnvNameCanNotBeBlank
+		e.Build.Status.Message = "name for environment variable must not be blank"
+		allErrs = append(allErrs, fmt.Errorf("%s", e.Build.Status.Message))
+	}
+
+	if envVar.Value != "" && envVar.ValueFrom != nil {
+		e.Build.Status.Reason = build.SpecEnvOnlyOneOfValueOrValueFromMustBeSpecified
+		e.Build.Status.Message = "only one of value or valueFrom must be specified"
+		allErrs = append(allErrs, fmt.Errorf("%s", e.Build.Status.Message))
+	}
+
+	return allErrs
+}
+
+// NewEnv instantiates a new Env passing the build object pointer along.
+func NewEnv(b *build.Build) *Env {
+	return &Env{Build: b}
+}
diff --git a/pkg/validate/validate.go b/pkg/validate/validate.go
index ddfb1d12..1d392a54 100644
--- a/pkg/validate/validate.go
+++ b/pkg/validate/validate.go
@@ -8,9 +8,10 @@ import (
 	"context"
 	"fmt"
 
-	build "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	build "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
 )
 
 const (
@@ -24,6 +25,8 @@ const (
 	Sources = "sources"
 	// BuildName for validating `metadata.name` entry
 	BuildName = "buildname"
+	// Envs for validating `spec.env` entries
+	Envs = "env"
 	// OwnerReferences for validating the ownerreferences between a Build
 	// and BuildRun objects
 	OwnerReferences = "ownerreferences"
@@ -58,6 +61,8 @@ func NewValidation(
 		return &SourcesRef{Build: build}, nil
 	case BuildName:
 		return &BuildNameRef{Build: build}, nil
+	case Envs:
+		return &Env{Build: build}, nil
 	default:
 		return nil, fmt.Errorf("unknown validation type")
 	}


Test patch:
diff --git a/pkg/bundle/bundle_suite_test.go b/pkg/bundle/bundle_suite_test.go
index 203657c7..d08f9e7a 100644
--- a/pkg/bundle/bundle_suite_test.go
+++ b/pkg/bundle/bundle_suite_test.go
@@ -1,3 +1,6 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
 package bundle_test
 
 import (
diff --git a/pkg/env/env_test.go b/pkg/env/env_test.go
new file mode 100644
index 00000000..18420280
--- /dev/null
+++ b/pkg/env/env_test.go
@@ -0,0 +1,296 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+package env
+
+import (
+	"reflect"
+	"testing"
+
+	corev1 "k8s.io/api/core/v1"
+)
+
+func TestMergeEnvVars(t *testing.T) {
+	type args struct {
+		new             []corev1.EnvVar
+		into            []corev1.EnvVar
+		overwriteValues bool
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    []corev1.EnvVar
+		wantErr bool
+	}{
+		{
+			name: "should not fail with nil inputs",
+			args: args{
+				new:             nil,
+				into:            nil,
+				overwriteValues: false,
+			},
+			want:    []corev1.EnvVar{},
+			wantErr: false,
+		},
+		{
+			name: "empty new and into should return empty",
+			args: args{
+				new:             []corev1.EnvVar{},
+				into:            []corev1.EnvVar{},
+				overwriteValues: true,
+			},
+			want:    []corev1.EnvVar{},
+			wantErr: false,
+		},
+		{
+			name: "empty new should return into",
+			args: args{
+				new: []corev1.EnvVar{},
+				into: []corev1.EnvVar{
+					{Name: "ONE", Value: "oneValue"},
+					{Name: "TWO", Value: "twoValue"},
+				},
+				overwriteValues: true,
+			},
+			want: []corev1.EnvVar{
+				{Name: "ONE", Value: "oneValue"},
+				{Name: "TWO", Value: "twoValue"},
+			},
+			wantErr: false,
+		},
+		{
+			name: "empty into should return new",
+			args: args{
+				new: []corev1.EnvVar{
+					{Name: "ONE", Value: "oneValue"},
+					{Name: "TWO", Value: "twoValue"},
+				},
+				into:            []corev1.EnvVar{},
+				overwriteValues: true,
+			},
+			want: []corev1.EnvVar{
+				{Name: "ONE", Value: "oneValue"},
+				{Name: "TWO", Value: "twoValue"},
+			},
+			wantErr: false,
+		},
+		{
+			name: "duplicate names should fail with overwriteValues false",
+			args: args{
+				new: []corev1.EnvVar{
+					{Name: "TWO", Value: "twoValueNew"},
+				},
+				into: []corev1.EnvVar{
+					{Name: "ONE", Value: "oneValue"},
+					{Name: "TWO", Value: "twoValue"},
+				},
+				overwriteValues: false,
+			},
+			want: []corev1.EnvVar{
+				{Name: "ONE", Value: "oneValue"},
+				{Name: "TWO", Value: "twoValue"},
+			},
+			wantErr: true,
+		},
+		{
+			name: "duplicate names should fail with overwriteValues false using valueFrom",
+			args: args{
+				new: []corev1.EnvVar{
+					{
+						Name: "TWO",
+						ValueFrom: &corev1.EnvVarSource{
+							FieldRef: &corev1.ObjectFieldSelector{
+								FieldPath: "my-field-path",
+							},
+						},
+					},
+				},
+				into: []corev1.EnvVar{
+					{Name: "ONE", Value: "oneValue"},
+					{Name: "TWO", Value: "twoValue"},
+				},
+				overwriteValues: false,
+			},
+			want: []corev1.EnvVar{
+				{Name: "ONE", Value: "oneValue"},
+				{Name: "TWO", Value: "twoValue"},
+			},
+			wantErr: true,
+		},
+		{
+			name: "duplicate names should succeed with overwriteValues true",
+			args: args{
+				new: []corev1.EnvVar{
+					{Name: "TWO", Value: "newTwoValue"},
+				},
+				into: []corev1.EnvVar{
+					{Name: "ONE", Value: "oneValue"},
+					{Name: "TWO", Value: "twoValue"},
+				},
+				overwriteValues: true,
+			},
+			want: []corev1.EnvVar{
+				{Name: "ONE", Value: "oneValue"},
+				{Name: "TWO", Value: "newTwoValue"},
+			},
+			wantErr: false,
+		},
+		{
+			name: "duplicate names should succeed with overwriteValues true using valueFrom",
+			args: args{
+				new: []corev1.EnvVar{
+					{
+						Name: "TWO",
+						ValueFrom: &corev1.EnvVarSource{
+							FieldRef: &corev1.ObjectFieldSelector{
+								FieldPath: "my-field-path",
+							},
+						},
+					},
+				},
+				into: []corev1.EnvVar{
+					{Name: "ONE", Value: "oneValue"},
+					{Name: "TWO", Value: "twoValue"},
+				},
+				overwriteValues: true,
+			},
+			want: []corev1.EnvVar{
+				{Name: "ONE", Value: "oneValue"},
+				{
+					Name: "TWO",
+					ValueFrom: &corev1.EnvVarSource{
+						FieldRef: &corev1.ObjectFieldSelector{
+							FieldPath: "my-field-path",
+						},
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "non-duplicate should succeed with overwriteValues false",
+			args: args{
+				new: []corev1.EnvVar{
+					{Name: "THREE", Value: "threeValue"},
+					{Name: "FOUR", Value: "fourValue"},
+				},
+				into: []corev1.EnvVar{
+					{Name: "ONE", Value: "oneValue"},
+					{Name: "TWO", Value: "twoValue"},
+				},
+				overwriteValues: false,
+			},
+			want: []corev1.EnvVar{
+				{Name: "ONE", Value: "oneValue"},
+				{Name: "TWO", Value: "twoValue"},
+				{Name: "THREE", Value: "threeValue"},
+				{Name: "FOUR", Value: "fourValue"},
+			},
+			wantErr: false,
+		},
+		{
+			name: "non-duplicate should succeed with overwriteValues false using valueFrom",
+			args: args{
+				new: []corev1.EnvVar{
+					{
+						Name: "THREE",
+						ValueFrom: &corev1.EnvVarSource{
+							FieldRef: &corev1.ObjectFieldSelector{
+								FieldPath: "my-field-path",
+							},
+						},
+					},
+					{Name: "FOUR", Value: "fourValue"},
+				},
+				into: []corev1.EnvVar{
+					{Name: "ONE", Value: "oneValue"},
+					{Name: "TWO", Value: "twoValue"},
+				},
+				overwriteValues: false,
+			},
+			want: []corev1.EnvVar{
+				{Name: "ONE", Value: "oneValue"},
+				{Name: "TWO", Value: "twoValue"},
+				{
+					Name: "THREE",
+					ValueFrom: &corev1.EnvVarSource{
+						FieldRef: &corev1.ObjectFieldSelector{
+							FieldPath: "my-field-path",
+						},
+					},
+				},
+				{Name: "FOUR", Value: "fourValue"},
+			},
+			wantErr: false,
+		},
+		{
+			name: "non-duplicate should succeed with overwriteValues true",
+			args: args{
+				new: []corev1.EnvVar{
+					{Name: "THREE", Value: "threeValue"},
+					{Name: "FOUR", Value: "fourValue"},
+				},
+				into: []corev1.EnvVar{
+					{Name: "ONE", Value: "oneValue"},
+					{Name: "TWO", Value: "twoValue"},
+				},
+				overwriteValues: true,
+			},
+			want: []corev1.EnvVar{
+				{Name: "ONE", Value: "oneValue"},
+				{Name: "TWO", Value: "twoValue"},
+				{Name: "THREE", Value: "threeValue"},
+				{Name: "FOUR", Value: "fourValue"},
+			},
+			wantErr: false,
+		},
+		{
+			name: "non-duplicate should succeed with overwriteValues true using valueFrom",
+			args: args{
+				new: []corev1.EnvVar{
+					{
+						Name: "THREE",
+						ValueFrom: &corev1.EnvVarSource{
+							FieldRef: &corev1.ObjectFieldSelector{
+								FieldPath: "my-field-path",
+							},
+						},
+					},
+					{Name: "FOUR", Value: "fourValue"},
+				},
+				into: []corev1.EnvVar{
+					{Name: "ONE", Value: "oneValue"},
+					{Name: "TWO", Value: "twoValue"},
+				},
+				overwriteValues: true,
+			},
+			want: []corev1.EnvVar{
+				{Name: "ONE", Value: "oneValue"},
+				{Name: "TWO", Value: "twoValue"},
+				{
+					Name: "THREE",
+					ValueFrom: &corev1.EnvVarSource{
+						FieldRef: &corev1.ObjectFieldSelector{
+							FieldPath: "my-field-path",
+						},
+					},
+				},
+				{Name: "FOUR", Value: "fourValue"},
+			},
+			wantErr: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := MergeEnvVars(tt.args.new, tt.args.into, tt.args.overwriteValues)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("MergeEnvVars() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("MergeEnvVars() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/pkg/reconciler/build/build_test.go b/pkg/reconciler/build/build_test.go
index 27a0af99..93de81df 100644
--- a/pkg/reconciler/build/build_test.go
+++ b/pkg/reconciler/build/build_test.go
@@ -477,5 +477,122 @@ var _ = Describe("Reconcile Build", func() {
 				Expect(reconcile.Result{}).To(Equal(result))
 			})
 		})
+
+		Context("when environment variables are specified", func() {
+			JustBeforeEach(func() {
+				buildSample.Spec.Source.Credentials = &corev1.LocalObjectReference{
+					Name: "existing",
+				}
+				buildSample.Spec.Output.Credentials = nil
+
+				// Fake some client Get calls and ensure we populate all
+				// different resources we could get during reconciliation
+				client.GetCalls(func(context context.Context, nn types.NamespacedName, object runtime.Object) error {
+					switch object := object.(type) {
+					case *build.Build:
+						buildSample.DeepCopyInto(object)
+					case *build.ClusterBuildStrategy:
+						clusterBuildStrategySample.DeepCopyInto(object)
+					case *corev1.Secret:
+						secretSample = ctl.SecretWithoutAnnotation("existing", namespace)
+						secretSample.DeepCopyInto(object)
+					}
+					return nil
+				})
+			})
+			It("fails when the name is blank", func() {
+				buildSample.Spec.Env = []corev1.EnvVar{
+					{
+						Name:  "",
+						Value: "some-value",
+					},
+				}
+
+				statusCall := ctl.StubFunc(corev1.ConditionFalse, build.SpecEnvNameCanNotBeBlank, "name for environment variable must not be blank")
+				statusWriter.UpdateCalls(statusCall)
+
+				_, err := reconciler.Reconcile(request)
+				Expect(err).To(BeNil())
+				Expect(statusWriter.UpdateCallCount()).To(Equal(1))
+
+			})
+			It("fails when the name is blank using valueFrom", func() {
+				buildSample.Spec.Env = []corev1.EnvVar{
+					{
+						Name: "",
+						ValueFrom: &corev1.EnvVarSource{
+							FieldRef: &corev1.ObjectFieldSelector{
+								FieldPath: "my-field-path",
+							},
+						},
+					},
+				}
+
+				statusCall := ctl.StubFunc(corev1.ConditionFalse, build.SpecEnvNameCanNotBeBlank, "name for environment variable must not be blank")
+				statusWriter.UpdateCalls(statusCall)
+
+				_, err := reconciler.Reconcile(request)
+				Expect(err).To(BeNil())
+				Expect(statusWriter.UpdateCallCount()).To(Equal(1))
+
+			})
+			It("fails when both value and valueFrom are specified", func() {
+				buildSample.Spec.Env = []corev1.EnvVar{
+					{
+						Name:  "some-name",
+						Value: "some-value",
+						ValueFrom: &corev1.EnvVarSource{
+							FieldRef: &corev1.ObjectFieldSelector{
+								FieldPath: "my-field-path",
+							},
+						},
+					},
+				}
+
+				statusCall := ctl.StubFunc(corev1.ConditionFalse, build.SpecEnvOnlyOneOfValueOrValueFromMustBeSpecified, "only one of value or valueFrom must be specified")
+				statusWriter.UpdateCalls(statusCall)
+
+				_, err := reconciler.Reconcile(request)
+				Expect(err).To(BeNil())
+				Expect(statusWriter.UpdateCallCount()).To(Equal(1))
+
+			})
+			It("succeeds with compliant env var using Value", func() {
+				buildSample.Spec.Env = []corev1.EnvVar{
+					{
+						Name:  "some-name",
+						Value: "some-value",
+					},
+				}
+
+				statusCall := ctl.StubFunc(corev1.ConditionTrue, build.BuildReason(build.Succeeded), "all validations succeeded")
+				statusWriter.UpdateCalls(statusCall)
+
+				_, err := reconciler.Reconcile(request)
+				Expect(err).To(BeNil())
+				Expect(statusWriter.UpdateCallCount()).To(Equal(1))
+
+			})
+			It("succeeds with compliant env var using ValueFrom", func() {
+				buildSample.Spec.Env = []corev1.EnvVar{
+					{
+						Name: "some-name",
+						ValueFrom: &corev1.EnvVarSource{
+							FieldRef: &corev1.ObjectFieldSelector{
+								FieldPath: "my-field-path",
+							},
+						},
+					},
+				}
+
+				statusCall := ctl.StubFunc(corev1.ConditionTrue, build.BuildReason(build.Succeeded), "all validations succeeded")
+				statusWriter.UpdateCalls(statusCall)
+
+				_, err := reconciler.Reconcile(request)
+				Expect(err).To(BeNil())
+				Expect(statusWriter.UpdateCallCount()).To(Equal(1))
+
+			})
+		})
 	})
 })
diff --git a/pkg/reconciler/buildrun/buildrun_test.go b/pkg/reconciler/buildrun/buildrun_test.go
index 23c4d609..ba8151ac 100644
--- a/pkg/reconciler/buildrun/buildrun_test.go
+++ b/pkg/reconciler/buildrun/buildrun_test.go
@@ -1234,5 +1234,104 @@ var _ = Describe("Reconcile BuildRun", func() {
 				Expect(resources.IsClientStatusUpdateError(err)).To(BeTrue())
 			})
 		})
+
+		Context("when environment variables are specified", func() {
+			JustBeforeEach(func() {
+
+			})
+			It("fails when the name is blank", func() {
+				buildRunSample.Spec.Env = []corev1.EnvVar{
+					{
+						Name:  "",
+						Value: "some-value",
+					},
+				}
+
+				statusCall := ctl.StubFunc(corev1.ConditionFalse, build.SpecEnvNameCanNotBeBlank, "name for environment variable must not be blank")
+				statusWriter.UpdateCalls(statusCall)
+
+				_, err := reconciler.Reconcile(buildRunRequest)
+				Expect(err).To(BeNil())
+				Expect(statusWriter.UpdateCallCount()).To(Equal(1))
+
+			})
+			It("fails when the name is blank using valueFrom", func() {
+				buildRunSample.Spec.Env = []corev1.EnvVar{
+					{
+						Name: "",
+						ValueFrom: &corev1.EnvVarSource{
+							FieldRef: &corev1.ObjectFieldSelector{
+								FieldPath: "my-field-path",
+							},
+						},
+					},
+				}
+
+				statusCall := ctl.StubFunc(corev1.ConditionFalse, build.SpecEnvNameCanNotBeBlank, "name for environment variable must not be blank")
+				statusWriter.UpdateCalls(statusCall)
+
+				_, err := reconciler.Reconcile(buildRunRequest)
+				Expect(err).To(BeNil())
+				Expect(statusWriter.UpdateCallCount()).To(Equal(1))
+
+			})
+			It("fails when both value and valueFrom are specified", func() {
+				buildRunSample.Spec.Env = []corev1.EnvVar{
+					{
+						Name:  "some-name",
+						Value: "some-value",
+						ValueFrom: &corev1.EnvVarSource{
+							FieldRef: &corev1.ObjectFieldSelector{
+								FieldPath: "my-field-path",
+							},
+						},
+					},
+				}
+
+				statusCall := ctl.StubFunc(corev1.ConditionFalse, build.SpecEnvOnlyOneOfValueOrValueFromMustBeSpecified, "only one of value or valueFrom must be specified")
+				statusWriter.UpdateCalls(statusCall)
+
+				_, err := reconciler.Reconcile(buildRunRequest)
+				Expect(err).To(BeNil())
+				Expect(statusWriter.UpdateCallCount()).To(Equal(1))
+
+			})
+			It("succeeds with compliant env var using Value", func() {
+				buildRunSample.Spec.Env = []corev1.EnvVar{
+					{
+						Name:  "some-name",
+						Value: "some-value",
+					},
+				}
+
+				statusCall := ctl.StubFunc(corev1.ConditionTrue, build.BuildReason(build.Succeeded), "all validations succeeded")
+				statusWriter.UpdateCalls(statusCall)
+
+				_, err := reconciler.Reconcile(buildRunRequest)
+				Expect(err).To(BeNil())
+				Expect(statusWriter.UpdateCallCount()).To(Equal(1))
+
+			})
+			It("succeeds with compliant env var using ValueFrom", func() {
+				buildRunSample.Spec.Env = []corev1.EnvVar{
+					{
+						Name: "some-name",
+						ValueFrom: &corev1.EnvVarSource{
+							FieldRef: &corev1.ObjectFieldSelector{
+								FieldPath: "my-field-path",
+							},
+						},
+					},
+				}
+
+				statusCall := ctl.StubFunc(corev1.ConditionTrue, build.BuildReason(build.Succeeded), "all validations succeeded")
+				statusWriter.UpdateCalls(statusCall)
+
+				_, err := reconciler.Reconcile(buildRunRequest)
+				Expect(err).To(BeNil())
+				Expect(statusWriter.UpdateCallCount()).To(Equal(1))
+
+			})
+		})
 	})
 })
diff --git a/pkg/reconciler/buildrun/resources/taskrun_test.go b/pkg/reconciler/buildrun/resources/taskrun_test.go
index c437e9c3..eedb4e4b 100644
--- a/pkg/reconciler/buildrun/resources/taskrun_test.go
+++ b/pkg/reconciler/buildrun/resources/taskrun_test.go
@@ -6,6 +6,7 @@ package resources_test
 
 import (
 	"fmt"
+	"reflect"
 	"strings"
 	"time"
 
@@ -19,6 +20,7 @@ import (
 
 	buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
 	"github.com/shipwright-io/build/pkg/config"
+	"github.com/shipwright-io/build/pkg/env"
 	"github.com/shipwright-io/build/pkg/reconciler/buildrun/resources"
 	"github.com/shipwright-io/build/test"
 	"github.com/shipwright-io/build/test/utils"
@@ -28,8 +30,12 @@ var _ = Describe("GenerateTaskrun", func() {
 
 	var (
 		build                  *buildv1alpha1.Build
+		buildWithEnvs          *buildv1alpha1.Build
 		buildRun               *buildv1alpha1.BuildRun
+		buildRunWithEnvs       *buildv1alpha1.BuildRun
 		buildStrategy          *buildv1alpha1.BuildStrategy
+		buildStrategyStepNames map[string]struct{}
+		buildStrategyWithEnvs  *buildv1alpha1.BuildStrategy
 		builderImage           *buildv1alpha1.Image
 		dockerfile, buildpacks string
 		ctl                    test.Catalog
@@ -183,6 +189,95 @@ var _ = Describe("GenerateTaskrun", func() {
 				}))
 			})
 		})
+
+		Context("when env vars are defined", func() {
+			BeforeEach(func() {
+				build, err = ctl.LoadBuildYAML([]byte(test.MinimalBuildahBuild))
+				Expect(err).To(BeNil())
+
+				buildWithEnvs, err = ctl.LoadBuildYAML([]byte(test.MinimalBuildahBuildWithEnvVars))
+				Expect(err).To(BeNil())
+
+				buildRun, err = ctl.LoadBuildRunFromBytes([]byte(test.MinimalBuildahBuildRun))
+				Expect(err).To(BeNil())
+
+				buildRunWithEnvs, err = ctl.LoadBuildRunFromBytes([]byte(test.MinimalBuildahBuildRunWithEnvVars))
+				Expect(err).To(BeNil())
+
+				buildStrategy, err = ctl.LoadBuildStrategyFromBytes([]byte(test.MinimalBuildahBuildStrategy))
+				Expect(err).To(BeNil())
+				buildStrategy.Spec.BuildSteps[0].ImagePullPolicy = "Always"
+				buildStrategyStepNames = make(map[string]struct{})
+				for _, step := range buildStrategy.Spec.BuildSteps {
+					buildStrategyStepNames[step.Name] = struct{}{}
+				}
+
+				buildStrategyWithEnvs, err = ctl.LoadBuildStrategyFromBytes([]byte(test.MinimalBuildahBuildStrategyWithEnvs))
+				Expect(err).To(BeNil())
+
+				expectedCommandOrArg = []string{
+					"bud", "--tag=$(params.shp-output-image)", fmt.Sprintf("--file=$(inputs.params.%s)", "DOCKERFILE"), "$(params.shp-source-context)",
+				}
+			})
+
+			It("should contain env vars specified in Build in every BuildStrategy step", func() {
+				got, err = resources.GenerateTaskSpec(config.NewDefaultConfig(), buildWithEnvs, buildRun, buildStrategy.Spec.BuildSteps, []buildv1alpha1.Parameter{})
+				Expect(err).To(BeNil())
+
+				combinedEnvs, err := env.MergeEnvVars(buildRun.Spec.Env, buildWithEnvs.Spec.Env, true)
+				Expect(err).NotTo(HaveOccurred())
+
+				for _, step := range got.Steps {
+					if _, ok := buildStrategyStepNames[step.Name]; ok {
+						Expect(len(step.Env)).To(Equal(len(combinedEnvs)))
+						Expect(reflect.DeepEqual(combinedEnvs, step.Env)).To(BeTrue())
+					} else {
+						Expect(reflect.DeepEqual(combinedEnvs, step.Env)).To(BeFalse())
+					}
+				}
+			})
+
+			It("should contain env vars specified in BuildRun in every step", func() {
+				got, err = resources.GenerateTaskSpec(config.NewDefaultConfig(), build, buildRunWithEnvs, buildStrategy.Spec.BuildSteps, []buildv1alpha1.Parameter{})
+				Expect(err).To(BeNil())
+
+				combinedEnvs, err := env.MergeEnvVars(buildRunWithEnvs.Spec.Env, build.Spec.Env, true)
+				Expect(err).NotTo(HaveOccurred())
+
+				for _, step := range got.Steps {
+					if _, ok := buildStrategyStepNames[step.Name]; ok {
+						Expect(len(step.Env)).To(Equal(len(combinedEnvs)))
+						Expect(reflect.DeepEqual(combinedEnvs, step.Env)).To(BeTrue())
+					} else {
+						Expect(reflect.DeepEqual(combinedEnvs, step.Env)).To(BeFalse())
+					}
+				}
+			})
+
+			It("should override Build env vars with BuildRun env vars in every step", func() {
+				got, err = resources.GenerateTaskSpec(config.NewDefaultConfig(), buildWithEnvs, buildRunWithEnvs, buildStrategy.Spec.BuildSteps, []buildv1alpha1.Parameter{})
+				Expect(err).To(BeNil())
+
+				combinedEnvs, err := env.MergeEnvVars(buildRunWithEnvs.Spec.Env, buildWithEnvs.Spec.Env, true)
+				Expect(err).NotTo(HaveOccurred())
+
+				for _, step := range got.Steps {
+					if _, ok := buildStrategyStepNames[step.Name]; ok {
+						Expect(len(step.Env)).To(Equal(len(combinedEnvs)))
+						Expect(reflect.DeepEqual(combinedEnvs, step.Env)).To(BeTrue())
+					} else {
+						Expect(reflect.DeepEqual(combinedEnvs, step.Env)).To(BeFalse())
+					}
+
+				}
+			})
+
+			It("should fail attempting to override an env var in a BuildStrategy", func() {
+				got, err = resources.GenerateTaskSpec(config.NewDefaultConfig(), buildWithEnvs, buildRunWithEnvs, buildStrategyWithEnvs.Spec.BuildSteps, []buildv1alpha1.Parameter{})
+				Expect(err).NotTo(BeNil())
+				Expect(err.Error()).To(Equal("error(s) occurred merging environment variables into BuildStrategy \"buildah\" steps: [environment variable \"MY_VAR_1\" already exists, environment variable \"MY_VAR_2\" already exists]"))
+			})
+		})
 	})
 
 	Describe("Generate the TaskRun", func() {
diff --git a/pkg/validate/envvars_test.go b/pkg/validate/envvars_test.go
new file mode 100644
index 00000000..8b701c25
--- /dev/null
+++ b/pkg/validate/envvars_test.go
@@ -0,0 +1,101 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package validate
+
+import (
+	"context"
+	"testing"
+
+	corev1 "k8s.io/api/core/v1"
+
+	build "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
+)
+
+func TestEnv_ValidatePath(t *testing.T) {
+	tests := []struct {
+		name       string
+		env        []corev1.EnvVar
+		wantErr    bool
+		errReason  string
+		errMessage string
+	}{
+		{
+			name: "empty env var name should fail",
+			env: []corev1.EnvVar{
+				{
+					Name:  "",
+					Value: "some-value",
+				},
+			},
+			wantErr:    true,
+			errReason:  string(build.SpecEnvNameCanNotBeBlank),
+			errMessage: "name for environment variable must not be blank",
+		},
+		{
+			name: "specifying both value and valueFrom should fail",
+			env: []corev1.EnvVar{
+				{
+					Name:  "some-name",
+					Value: "some-value",
+					ValueFrom: &corev1.EnvVarSource{
+						FieldRef: &corev1.ObjectFieldSelector{
+							FieldPath: "my-field-path",
+						},
+					},
+				},
+			},
+			wantErr:    true,
+			errReason:  string(build.SpecEnvOnlyOneOfValueOrValueFromMustBeSpecified),
+			errMessage: "only one of value or valueFrom must be specified",
+		},
+		{
+			name: "compliant env var should pass",
+			env: []corev1.EnvVar{
+				{
+					Name:  "some-name",
+					Value: "some-value",
+				},
+			},
+			wantErr:    false,
+			errReason:  "",
+			errMessage: "",
+		},
+		{
+			name: "compliant env var using valueFrom should pass",
+			env: []corev1.EnvVar{
+				{
+					Name: "some-name",
+					ValueFrom: &corev1.EnvVarSource{
+						FieldRef: &corev1.ObjectFieldSelector{
+							FieldPath: "my-field-path",
+						},
+					},
+				},
+			},
+			wantErr:    false,
+			errReason:  "",
+			errMessage: "",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			b := &build.Build{
+				Spec: build.BuildSpec{
+					Env: tt.env,
+				},
+			}
+			e := NewEnv(b)
+			if err := e.ValidatePath(context.Background()); (err != nil) != tt.wantErr {
+				t.Errorf("Env.ValidatePath() error = %v, wantErr %v", err, tt.wantErr)
+			}
+			if b.Status.Reason != build.BuildReason(tt.errReason) {
+				t.Errorf("Build.Status.Reason = %v, wanted: %v", b.Status.Reason, tt.errReason)
+			}
+			if b.Status.Message != tt.errMessage {
+				t.Errorf("Build.Status.Message = %v, wanted: %v", b.Status.Message, tt.errMessage)
+			}
+		})
+	}
+}
diff --git a/test/build_samples.go b/test/build_samples.go
index 209d43c0..a8d9abfc 100644
--- a/test/build_samples.go
+++ b/test/build_samples.go
@@ -4,6 +4,29 @@
 
 package test
 
+// MinimalBuildahBuildWithEnvVars defines a simple
+// Build with a source, strategy, and env vars
+const MinimalBuildahBuildWithEnvVars = `
+apiVersion: shipwright.io/v1alpha1
+kind: Build
+metadata:
+  name: buildah
+spec:
+  source:
+    url: "https://github.com/shipwright-io/sample-go"
+  strategy:
+    name: buildah
+    kind: ClusterBuildStrategy
+  dockerfile: Dockerfile
+  env:
+    - name: MY_VAR_1
+      value: "my-var-1-build-value"
+    - name: MY_VAR_2
+      valueFrom:
+        fieldRef:
+          fieldPath: "my-fieldpath"
+`
+
 // MinimalBuildahBuild defines a simple
 // Build with a source and a strategy
 const MinimalBuildahBuild = `
diff --git a/test/buildrun_samples.go b/test/buildrun_samples.go
index 5c614170..504da8e8 100644
--- a/test/buildrun_samples.go
+++ b/test/buildrun_samples.go
@@ -4,6 +4,25 @@
 
 package test
 
+// MinimalBuildahBuildRunWithEnvVars defines a simple
+// BuildRun with a referenced Build and env vars
+const MinimalBuildahBuildRunWithEnvVars = `
+apiVersion: shipwright.io/v1alpha1
+kind: BuildRun
+metadata:
+  name: buildah-run
+spec:
+  buildRef:
+    name: buildah
+  env:
+    - name: MY_VAR_2
+      value: "my-var-2-buildrun-value"
+    - name: MY_VAR_3
+      valueFrom:
+        fieldRef:
+          fieldPath: "my-fieldpath"
+`
+
 // MinimalBuildahBuildRun defines a simple
 // BuildRun with a referenced Build
 const MinimalBuildahBuildRun = `
diff --git a/test/buildstrategy_samples.go b/test/buildstrategy_samples.go
index 73355fdb..b70f976f 100644
--- a/test/buildstrategy_samples.go
+++ b/test/buildstrategy_samples.go
@@ -58,6 +58,68 @@ spec:
           mountPath: /var/lib/containers/storage
 `
 
+// MinimalBuildahBuildStrategyWithEnvs defines a
+// BuildStrategy for Buildah with two steps
+// each of them with different container resources
+// and env vars
+const MinimalBuildahBuildStrategyWithEnvs = `
+apiVersion: shipwright.io/v1alpha1
+kind: BuildStrategy
+metadata:
+  name: buildah
+spec:
+  buildSteps:
+    - name: buildah-bud
+      image: quay.io/containers/buildah:v1.20.1
+      workingDir: $(params.shp-source-root)
+      securityContext:
+        privileged: true
+      command:
+        - /usr/bin/buildah
+      args:
+        - bud
+        - --tag=$(params.shp-output-image)
+        - --file=$(build.dockerfile)
+        - $(params.shp-source-context)
+      resources:
+        limits:
+          cpu: 500m
+          memory: 1Gi
+        requests:
+          cpu: 500m
+          memory: 1Gi
+      volumeMounts:
+        - name: buildah-images
+          mountPath: /var/lib/containers/storage
+      env:
+        - name: MY_VAR_1
+          value: "my-var-1-buildstrategy-value"
+        - name: MY_VAR_2
+          valueFrom:
+            fieldRef:
+              fieldPath: "my-fieldpath"
+    - name: buildah-push
+      image: quay.io/containers/buildah:v1.20.1
+      securityContext:
+        privileged: true
+      command:
+        - /usr/bin/buildah
+      args:
+        - push
+        - --tls-verify=false
+        - docker://$(params.shp-output-image)
+      resources:
+        limits:
+          cpu: 100m
+          memory: 65Mi
+        requests:
+          cpu: 100m
+          memory: 65Mi
+      volumeMounts:
+        - name: buildah-images
+          mountPath: /var/lib/containers/storage
+`
+
 // BuildahBuildStrategySingleStep defines a
 // BuildStrategy for Buildah with a single step
 // and container resources
diff --git a/test/bundle/README.md b/test/bundle/README.md
index daed39f4..6d67d327 100644
--- a/test/bundle/README.md
+++ b/test/bundle/README.md
@@ -1,3 +1,8 @@
+<!--
+Copyright The Shipwright Contributors
+
+SPDX-License-Identifier: Apache-2.0
+-->
 # Bundle Test
 
 Test file for bundle functions.