diff --git a/README.rst b/README.rst
index 1acae81..738e6ed 100644
--- a/README.rst
+++ b/README.rst
@@ -75,6 +75,13 @@ happy can find it later.
   logged in through Heroku CLI, i.e. your token is stored in your ``netrc``
   file.
 
+- ``--env``
+
+  (optional) Environment variable overrides, e.g. ``--env KEY=value``. For
+  multiple variables, this option can be passed more than once. Variable names
+  MUST match one of the names in the ``env`` section of your ``app.json``, or
+  the build will fail with an ``invalid app.json`` message.
+
 - ``--tarball-url``
 
   (optional) URL of the tarball containing app.json. If this is not given,
diff --git a/happy/__init__.py b/happy/__init__.py
index a59c152..b5be08e 100644
--- a/happy/__init__.py
+++ b/happy/__init__.py
@@ -15,13 +15,14 @@ class Happy(object):
         """
         self._api = Heroku(auth_token=auth_token)
 
-    def create(self, tarball_url):
+    def create(self, tarball_url, env=None):
         """Creates a Heroku app-setup build.
 
         :param tarball_url: URL of a tarball containing an ``app.json``.
+        :param env: Dict containing environment variable overrides.
         :returns: A tuple with ``(build_id, app_name)``.
         """
-        data = self._api.create_build(tarball_url=tarball_url)
+        data = self._api.create_build(tarball_url=tarball_url, env=env)
 
         return (data['id'], data['app']['name'])
 
diff --git a/happy/cli.py b/happy/cli.py
index 4636560..76083e4 100644
--- a/happy/cli.py
+++ b/happy/cli.py
@@ -52,7 +52,8 @@ def cli():
 @cli.command(name='up')
 @click.option('--tarball-url', help='URL of the tarball containing app.json.')
 @click.option('--auth-token', help='Heroku API auth token.')
-def up(tarball_url, auth_token):
+@click.option('--env', multiple=True, help='Env override, e.g. KEY=value.')
+def up(tarball_url, auth_token, env):
     """Brings up a Heroku app."""
     tarball_url = tarball_url or _infer_tarball_url()
 
@@ -60,11 +61,18 @@ def up(tarball_url, auth_token):
         click.echo('No tarball URL found.')
         sys.exit(1)
 
+    if env:
+        # Split ["KEY=value", ...] into {"KEY": "value", ...}
+        env = {
+            arg.split('=')[0]: arg.split('=')[1]
+            for arg in env
+        }
+
     happy = Happy(auth_token=auth_token)
 
     click.echo('Creating app... ', nl=False)
 
-    build_id, app_name = happy.create(tarball_url=tarball_url)
+    build_id, app_name = happy.create(tarball_url=tarball_url, env=env)
 
     click.echo(app_name)
 
diff --git a/happy/heroku.py b/happy/heroku.py
index a5f95c9..9684b34 100644
--- a/happy/heroku.py
+++ b/happy/heroku.py
@@ -66,10 +66,11 @@ class Heroku(object):
 
         return response.json()
 
-    def create_build(self, tarball_url):
+    def create_build(self, tarball_url, env=None):
         """Creates an app-setups build. Returns response data as a dict.
 
         :param tarball_url: URL of a tarball containing an ``app.json``.
+        :param env: Dict containing environment variable overrides.
         :returns: Response data as a ``dict``.
         """
         data = {
@@ -78,6 +79,9 @@ class Heroku(object):
             }
         }
 
+        if env:
+            data['overrides'] = {'env': env}
+
         return self.api_request('POST', '/app-setups', data=data)
 
     def check_build_status(self, build_id):

Add an --env option to pass env overrides
There should be an `--env` option for `happy up` that allows passing env overrides to the `app-setups` endpoint.
diff --git a/tests/test_cli.py b/tests/test_cli.py
index b613880..2bfaeb8 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -96,6 +96,26 @@ def test_up_no_tarball_url(happy, runner):
     assert 'no tarball' in result.output.lower()
 
 
+def test_up_env(happy, runner):
+    """Running up --env should pass environment overrides."""
+    with runner.isolated_filesystem():
+        runner.invoke(cli, [
+            'up',
+            '--env',
+            'FART=test',
+            '--env',
+            'BUTT=wow',
+            '--tarball-url=example.com',
+        ])
+
+    args_, kwargs = happy().create.call_args
+
+    assert kwargs['env'] == {
+        'FART': 'test',
+        'BUTT': 'wow',
+    }
+
+
 def test_up_writes_app_name(happy, runner):
     """Running up should write the app name to .happy."""
     with runner.isolated_filesystem():
diff --git a/tests/test_happy.py b/tests/test_happy.py
index a5bdb25..f343079 100644
--- a/tests/test_happy.py
+++ b/tests/test_happy.py
@@ -39,7 +39,20 @@ def test_create(heroku, happy):
     """Should create an app build on Heroku."""
     happy.create(tarball_url='tarball-url')
 
-    heroku().create_build.assert_called_with(tarball_url='tarball-url')
+    heroku().create_build.assert_called_with(
+        env=None,
+        tarball_url='tarball-url',
+    )
+
+
+def test_create_env(heroku, happy):
+    """Should pass env overrides to Heroku.create_build."""
+    happy.create(tarball_url='tarball-url', env={'TEST': 'env'})
+
+    heroku().create_build.assert_called_with(
+        tarball_url='tarball-url',
+        env={'TEST': 'env'},
+    )
 
 
 def test_create_returns_app_name(heroku, happy):
diff --git a/tests/test_heroku.py b/tests/test_heroku.py
index 2ea21f2..5a7a5e5 100644
--- a/tests/test_heroku.py
+++ b/tests/test_heroku.py
@@ -124,6 +124,23 @@ def test_heroku_create_build(api_request):
     assert result == fake_json
 
 
+@mock.patch.object(Heroku, 'api_request')
+def test_heroku_create_build_env(api_request):
+    """Heroku.create_build should send a POST to /app-setups."""
+    heroku = Heroku()
+
+    result = heroku.create_build('tarball-url', env={'HELLO': 'world'})
+
+    api_request.assert_called_with(
+        'POST',
+        '/app-setups',
+        data={
+            'source_blob': {'url': 'tarball-url'},
+            'overrides': {'env': {'HELLO': 'world'}},
+        },
+    )
+
+
 @mock.patch.object(Heroku, 'api_request')
 def test_heroku_check_build_status(api_request):
     """Heroku.check_build_status should send a GET to /app-setups/:id."""

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-flake8 mock
apt-get update apt-get install -y gcc
3.9
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
certifi==2025.1.31
charset-normalizer==3.4.1
click==8.1.8
coverage==7.8.0
exceptiongroup==1.2.2
flake8==7.2.0
-e git+https://github.com/grampajoe/happy.git@7121a2d81b511fd198624ddb37526ef541ffd2c6#egg=happy
idna==3.10
iniconfig==2.1.0
mccabe==0.7.0
mock==5.2.0
packaging==24.2
pluggy==1.5.0
pycodestyle==2.13.0
pyflakes==3.3.2
pytest==8.3.5
pytest-cov==6.0.0
pytest-flake8==1.3.0
requests==2.32.3
tomli==2.2.1
urllib3==2.3.0

name: happy
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - certifi==2025.1.31
      - charset-normalizer==3.4.1
      - click==8.1.8
      - coverage==7.8.0
      - exceptiongroup==1.2.2
      - flake8==7.2.0
      - idna==3.10
      - iniconfig==2.1.0
      - mccabe==0.7.0
      - mock==5.2.0
      - packaging==24.2
      - pluggy==1.5.0
      - pycodestyle==2.13.0
      - pyflakes==3.3.2
      - pytest==8.3.5
      - pytest-cov==6.0.0
      - pytest-flake8==1.3.0
      - requests==2.32.3
      - tomli==2.2.1
      - urllib3==2.3.0
prefix: /opt/conda/envs/happy

tests/test_cli.py::test_up_env tests/test_happy.py::test_create tests/test_happy.py::test_create_env tests/test_heroku.py::test_heroku_create_build_env
tests/test_cli.py::test_help tests/test_cli.py::test_up tests/test_cli.py::test_up_auth_token tests/test_cli.py::test_up_tarball_url tests/test_cli.py::test_up_tarball_url_app_json tests/test_cli.py::test_up_no_tarball_url tests/test_cli.py::test_up_writes_app_name tests/test_cli.py::test_up_waits_for_build tests/test_cli.py::test_up_prints_info tests/test_cli.py::test_down tests/test_cli.py::test_down_auth_token tests/test_cli.py::test_down_deletes_app_name_file tests/test_cli.py::test_down_no_app tests/test_cli.py::test_down_prints_info tests/test_happy.py::test_auth_token tests/test_happy.py::test_create_returns_app_name tests/test_happy.py::test_wait tests/test_happy.py::test_delete tests/test_heroku.py::test_heroku tests/test_heroku.py::test_heroku_api_request tests/test_heroku.py::test_heroku_api_request_headers tests/test_heroku.py::test_heroku_api_request_auth_token tests/test_heroku.py::test_heroku_api_request_fail tests/test_heroku.py::test_heroku_api_request_big_fail tests/test_heroku.py::test_heroku_create_build tests/test_heroku.py::test_heroku_check_build_status tests/test_heroku.py::test_heroku_check_build_status_pending tests/test_heroku.py::test_heroku_check_build_status_succeeded tests/test_heroku.py::test_heroku_check_build_status_failed tests/test_heroku.py::test_heroku_delete_app
README.rst happy/__init__.py happy/cli.py happy/heroku.py
README.rst happy/__init__.py happy/cli.py happy/heroku.py