Skip to content

Commit d6d0917

Browse files
Pedro Piñera Buendíaclaude
authored andcommitted
feat: Deploy only app code, install Elixir via mise on remote
Only deploy application code paths to the sandbox (~2MB), filtering out OTP and Elixir stdlib paths (~58MB) that are already installed on the remote via mise. Also install Elixir alongside Erlang so the remote peer has access to the full Elixir stdlib. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 912c963 commit d6d0917

File tree

1 file changed

+45
-21
lines changed

1 file changed

+45
-21
lines changed

lib/terrarium/runtime.ex

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ defmodule Terrarium.Runtime do
3939
:telemetry.span([:terrarium, :replicate], %{sandbox: sandbox, otp_version: otp_version}, fn ->
4040
with :ok <- ensure_mise(sandbox),
4141
{:ok, dest} <- resolve_dest(sandbox, dest),
42-
{:ok, erl_path} <- install_erlang(sandbox, otp_version),
42+
{:ok, runtime} <- install_erlang(sandbox, otp_version),
4343
:ok <- deploy_code(sandbox, dest),
44-
{:ok, pid, node} <- start_peer(sandbox, erl_path, dest, opts) do
44+
{:ok, pid, node} <- start_peer(sandbox, runtime, dest, opts) do
4545
Logger.info("Runtime started in sandbox",
4646
sandbox_id: sandbox.id,
4747
node: node,
@@ -132,28 +132,38 @@ defmodule Terrarium.Runtime do
132132
defp install_erlang(sandbox, otp_version) do
133133
remote_home = resolve_remote_home(sandbox)
134134
mise = "#{remote_home}/.local/bin/mise"
135+
elixir_version = System.version()
135136

136-
Logger.info("Installing Erlang #{otp_version} via mise", sandbox_id: sandbox.id)
137+
Logger.info("Installing Erlang #{otp_version} and Elixir #{elixir_version} via mise",
138+
sandbox_id: sandbox.id
139+
)
137140

138-
case Terrarium.exec(sandbox, "#{mise} install erlang@#{otp_version}", timeout: 600_000) do
139-
{:ok, %{exit_code: 0}} ->
140-
# Get the install path to find the erl binary
141-
case Terrarium.exec(sandbox, "#{mise} where erlang@#{otp_version}") do
142-
{:ok, %{exit_code: 0, stdout: path}} ->
143-
erl_path = Path.join(String.trim(path), "bin/erl")
144-
Logger.info("Erlang #{otp_version} ready", sandbox_id: sandbox.id, erl_path: erl_path)
145-
{:ok, erl_path}
141+
install_cmd = "#{mise} install erlang@#{otp_version} elixir@#{elixir_version}"
146142

147-
{:ok, %{exit_code: code, stderr: stderr}} ->
148-
{:error, {:mise_where_failed, code, stderr}}
143+
case Terrarium.exec(sandbox, install_cmd, timeout: 600_000) do
144+
{:ok, %{exit_code: 0}} ->
145+
with {:ok, %{exit_code: 0, stdout: erl_where}} <-
146+
Terrarium.exec(sandbox, "#{mise} where erlang@#{otp_version}"),
147+
{:ok, %{exit_code: 0, stdout: elixir_where}} <-
148+
Terrarium.exec(sandbox, "#{mise} where elixir@#{elixir_version}") do
149+
erl_path = Path.join(String.trim(erl_where), "bin/erl")
150+
elixir_lib = Path.join(String.trim(elixir_where), "lib")
151+
152+
Logger.info("Runtime ready",
153+
sandbox_id: sandbox.id,
154+
erl_path: erl_path,
155+
elixir_lib: elixir_lib
156+
)
149157

150-
{:error, reason} ->
151-
{:error, reason}
158+
{:ok, %{erl_path: erl_path, elixir_lib: elixir_lib}}
159+
else
160+
{:ok, %{exit_code: code, stderr: stderr}} -> {:error, {:mise_where_failed, code, stderr}}
161+
{:error, reason} -> {:error, reason}
152162
end
153163

154164
{:ok, %{exit_code: code, stderr: stderr}} ->
155-
Logger.error("Erlang installation failed", sandbox_id: sandbox.id, reason: stderr)
156-
{:error, {:erlang_install_failed, code, stderr}}
165+
Logger.error("Installation failed", sandbox_id: sandbox.id, reason: stderr)
166+
{:error, {:install_failed, code, stderr}}
157167

158168
{:error, reason} ->
159169
{:error, reason}
@@ -172,12 +182,20 @@ defmodule Terrarium.Runtime do
172182
# ============================================================================
173183

174184
defp deploy_code(sandbox, dest) do
185+
# Only deploy application code paths, not OTP or Elixir stdlib.
186+
# Those are already available on the remote via mise.
187+
otp_lib = :code.lib_dir() |> List.to_string()
188+
elixir_lib = :code.lib_dir(:elixir) |> List.to_string() |> Path.dirname()
189+
175190
paths =
176191
:code.get_path()
177192
|> Enum.map(&List.to_string/1)
178193
|> Enum.filter(&File.dir?/1)
194+
|> Enum.reject(fn p ->
195+
String.starts_with?(p, otp_lib) or String.starts_with?(p, elixir_lib)
196+
end)
179197

180-
Logger.debug("Creating tarball from #{length(paths)} code paths", sandbox_id: sandbox.id)
198+
Logger.debug("Creating tarball from #{length(paths)} code paths (app only)", sandbox_id: sandbox.id)
181199

182200
tarball_path = Path.join(System.tmp_dir!(), "terrarium_deploy_#{System.unique_integer([:positive])}.tar.gz")
183201
file_args = Enum.flat_map(paths, fn path -> ["-C", Path.dirname(path), Path.basename(path)] end)
@@ -215,12 +233,18 @@ defmodule Terrarium.Runtime do
215233
# Peer Node
216234
# ============================================================================
217235

218-
defp start_peer(sandbox, erl_path, dest, opts) do
236+
defp start_peer(sandbox, runtime, dest, opts) do
237+
# Include app code + Elixir stdlib paths
238+
pa_paths = [
239+
"#{dest}/ebin",
240+
"#{runtime.elixir_lib}/*/ebin"
241+
]
242+
219243
peer_opts =
220244
opts
221245
|> Keyword.take([:name, :env, :erl_args])
222-
|> Keyword.put(:pa_paths, ["#{dest}/ebin"])
223-
|> Keyword.put(:erl_cmd, erl_path)
246+
|> Keyword.put(:pa_paths, pa_paths)
247+
|> Keyword.put(:erl_cmd, runtime.erl_path)
224248

225249
Terrarium.Peer.start(sandbox, peer_opts)
226250
end

0 commit comments

Comments
 (0)