diff --git a/bin/hermit.hcl b/bin/hermit.hcl
index 482702cd7a..461e6f37a2 100644
--- a/bin/hermit.hcl
+++ b/bin/hermit.hcl
@@ -1,6 +1,5 @@
 env = {
   "DBMATE_MIGRATIONS_DIR": "${HERMIT_ENV}/backend/controller/sql/schema",
-  "FTL_ENDPOINT": "http://localhost:8892",
   "FTL_INIT_GO_REPLACE": "github.com/TBD54566975/ftl=${HERMIT_ENV}",
   "FTL_SOURCE": "${HERMIT_ENV}",
   "OTEL_GRPC_PORT": "4317",
diff --git a/deployment/Justfile b/deployment/Justfile
index 3e198a28d6..dc66abb94e 100755
--- a/deployment/Justfile
+++ b/deployment/Justfile
@@ -41,7 +41,7 @@ setup-registry:
 setup-cluster: setup-registry
   #!/bin/bash
   if [ -z "$(k3d cluster list | grep ftl)" ]; then
-  k3d cluster create ftl --api-port 6550 -p "8892:80@loadbalancer"  --agents 2 \
+  k3d cluster create ftl --api-port 6550 -p "8893:80@loadbalancer" --agents 2 \
       --registry-use {{registry_full}} \
       --registry-config '{{mirrors}}' && \  # Start installing the DB for performance reasons
       kubectl apply -k base/db-create || sleep 5 && \ # wait for CRDs to be created, the initial apply will usually fail
diff --git a/internal/integration/actions.go b/internal/integration/actions.go
index ba7cfb402f..93833c56d3 100644
--- a/internal/integration/actions.go
+++ b/internal/integration/actions.go
@@ -160,7 +160,9 @@ func DebugShell() Action {
 func Exec(cmd string, args ...string) Action {
 	return func(t testing.TB, ic TestContext) {
 		Infof("Executing (in %s): %s %s", ic.workDir, cmd, shellquote.Join(args...))
-		err := ftlexec.Command(ic, log.Debug, ic.workDir, cmd, args...).RunStderrError(ic)
+		command := ftlexec.Command(ic, log.Debug, ic.workDir, cmd, args...)
+		command.Env = append(command.Env, "FTL_ENDPOINT=http://127.0.0.1:"+TestPort)
+		err := command.RunStderrError(ic)
 		assert.NoError(t, err)
 	}
 }
@@ -181,6 +183,7 @@ func ExecWithExpectedOutput(want string, cmd string, args ...string) Action {
 func ExecWithExpectedError(want string, cmd string, args ...string) Action {
 	return func(t testing.TB, ic TestContext) {
 		Infof("Executing: %s %s", cmd, shellquote.Join(args...))
+		t.Setenv("FTL_ENDPOINT", "http://127.0.0.1:"+TestPort)
 		output, err := ftlexec.Capture(ic, ic.workDir, cmd, args...)
 		assert.Error(t, err)
 		assert.Contains(t, string(output), want)
@@ -222,9 +225,9 @@ func Deploy(module string) Action {
 	return Chain(
 		func(t testing.TB, ic TestContext) {
 			if ic.kube {
-				Exec("ftl", "deploy", "--build-env", "GOOS=linux", "--build-env", "GOARCH=amd64", "--build-env", "CGO_ENABLED=0", module)(t, ic)
+				Exec("ftl", "deploy", "--endpoint", "http://127.0.0.1:"+TestPort, "--build-env", "GOOS=linux", "--build-env", "GOARCH=amd64", "--build-env", "CGO_ENABLED=0", module)(t, ic)
 			} else {
-				Exec("ftl", "deploy", module)(t, ic)
+				Exec("ftl", "deploy", "--endpoint", "http://127.0.0.1:"+TestPort, module)
 			}
 		},
 		Wait(module),
@@ -527,7 +530,7 @@ func JsonData(t testing.TB, body interface{}) []byte {
 func HttpCall(method string, path string, headers map[string][]string, body []byte, onResponse func(t testing.TB, resp *HTTPResponse)) Action {
 	return func(t testing.TB, ic TestContext) {
 		Infof("HTTP %s %s", method, path)
-		baseURL, err := url.Parse(fmt.Sprintf("http://localhost:8891"))
+		baseURL, err := url.Parse(fmt.Sprintf("http://localhost:" + TestIngressPort))
 		assert.NoError(t, err)
 
 		u, err := baseURL.Parse(path)
diff --git a/internal/integration/harness.go b/internal/integration/harness.go
index 396e4da1a6..f8b30e8d25 100644
--- a/internal/integration/harness.go
+++ b/internal/integration/harness.go
@@ -30,6 +30,9 @@ import (
 	"github.com/TBD54566975/ftl/internal/rpc"
 )
 
+const TestPort = "9892"
+const TestIngressPort = "9891"
+
 func integrationTestTimeout() time.Duration {
 	timeout := optional.Zero(os.Getenv("FTL_INTEGRATION_TEST_TIMEOUT")).Default("5s")
 	d, err := time.ParseDuration(timeout)
@@ -199,23 +202,24 @@ func run(t *testing.T, actionsOrOptions ...ActionOrOption) {
 			assert.NoError(t, err)
 		}
 	})
+	t.Setenv("FTL_ENDPOINT", "http://127.0.0.1:"+TestPort)
 
 	for _, language := range opts.languages {
 		ctx, done := context.WithCancel(ctx)
 		t.Run(language, func(t *testing.T) {
 			tmpDir := initWorkDir(t, cwd, opts)
 
-			verbs := rpc.Dial(ftlv1connect.NewVerbServiceClient, "http://localhost:8892", log.Debug)
+			verbs := rpc.Dial(ftlv1connect.NewVerbServiceClient, "http://localhost:"+TestPort, log.Debug)
 
 			var controller ftlv1connect.ControllerServiceClient
 			var console pbconsoleconnect.ConsoleServiceClient
 			if opts.startController {
 				Infof("Starting ftl cluster")
-				ctx = startProcess(ctx, t, filepath.Join(binDir, "ftl"), "serve", "--recreate")
+				ctx = startProcess(ctx, t, filepath.Join(binDir, "ftl"), "serve", "--db-port", "15433", "--recreate", "--bind", "http://127.0.0.1:"+TestIngressPort)
 			}
 			if opts.startController || opts.kube {
-				controller = rpc.Dial(ftlv1connect.NewControllerServiceClient, "http://localhost:8892", log.Debug)
-				console = rpc.Dial(pbconsoleconnect.NewConsoleServiceClient, "http://localhost:8892", log.Debug)
+				controller = rpc.Dial(ftlv1connect.NewControllerServiceClient, "http://localhost:"+TestPort, log.Debug)
+				console = rpc.Dial(pbconsoleconnect.NewConsoleServiceClient, "http://localhost:"+TestPort, log.Debug)
 			}
 
 			testData := filepath.Join(cwd, "testdata", language)