mirror of
https://github.com/livebook-dev/livebook.git
synced 2024-09-20 10:05:57 +08:00
Setup Docker image with releases (#244)
* Setup Docker image with releases * Support ip env variable and use it for Docker deployment * Autofocus auth forms * Rename ip env var * Update option lists * Make distribution cookie configurable * Update README.md Co-authored-by: José Valim <jose.valim@dashbit.co> * Include git in the final image * Remove unnecessary build dependency * Improve file permissions handling and add more comments * Use namespaced home directory * Update README with all running options * Update base image * Reference official Docker image in the README Co-authored-by: José Valim <jose.valim@dashbit.co>
This commit is contained in:
parent
cc2820f17e
commit
ac8e1e30f4
15
.dockerignore
Normal file
15
.dockerignore
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Mirrors .gitignore
|
||||
|
||||
/_build/
|
||||
/cover/
|
||||
/deps/
|
||||
/doc/
|
||||
/.fetch
|
||||
erl_crash.dump
|
||||
*.ez
|
||||
livebook-*.tar
|
||||
npm-debug.log
|
||||
/assets/node_modules/
|
||||
/priv/static_dev/
|
||||
/tmp/
|
||||
/livebook
|
66
Dockerfile
Normal file
66
Dockerfile
Normal file
|
@ -0,0 +1,66 @@
|
|||
# Stage 1
|
||||
# Builds the Livebook release
|
||||
FROM hexpm/elixir:1.12.0-rc.1-erlang-24.0-rc3-alpine-3.13.3 AS build
|
||||
|
||||
RUN apk add --no-cache build-base git
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install hex and rebar
|
||||
RUN mix local.hex --force && \
|
||||
mix local.rebar --force
|
||||
|
||||
# Build for production
|
||||
ENV MIX_ENV=prod
|
||||
|
||||
# Install mix dependencies
|
||||
COPY mix.exs mix.lock ./
|
||||
COPY config config
|
||||
RUN mix do deps.get, deps.compile
|
||||
|
||||
# Compile and build the release
|
||||
COPY priv priv
|
||||
COPY lib lib
|
||||
# We need README.md during compilation
|
||||
# (look for @external_resource "README.md")
|
||||
COPY README.md README.md
|
||||
RUN mix do compile, release
|
||||
|
||||
# Stage 2
|
||||
# Prepares the runtime environment and copies over the relase.
|
||||
# We use the same base image, because we need Erlang, Elixir and Mix
|
||||
# during runtime to spawn the Livebook standalone runtimes.
|
||||
# Consequently the release doesn't include ERTS as we have it anyway.
|
||||
FROM hexpm/elixir:1.12.0-rc.1-erlang-24.0-rc3-alpine-3.13.3
|
||||
|
||||
RUN apk add --no-cache \
|
||||
# Runtime dependencies
|
||||
openssl ncurses-libs \
|
||||
# In case someone uses `Mix.install/2` and point to a git repo
|
||||
git
|
||||
|
||||
# Run in the /data directory by default, makes for
|
||||
# a good place for the user to mount local volume
|
||||
WORKDIR /data
|
||||
|
||||
ENV HOME=/home/livebook
|
||||
# Make sure someone running the container with `--user`
|
||||
# has permissions to the home dir (for `Mix.install/2` cache)
|
||||
RUN mkdir $HOME && chmod 777 $HOME
|
||||
|
||||
# Install hex and rebar for `Mix.install/2` and Mix runtime
|
||||
RUN mix local.hex --force && \
|
||||
mix local.rebar --force
|
||||
|
||||
# Override the default 127.0.0.1 address, so that the app
|
||||
# can be accessed outside the container by binding ports
|
||||
ENV LIVEBOOK_IP 0.0.0.0
|
||||
|
||||
# Copy the release build from the previous stage
|
||||
COPY --from=build /app/_build/prod/rel/livebook /app
|
||||
|
||||
# Make release executables available to any user,
|
||||
# in case someone runs the container with `--user`
|
||||
RUN find /app -executable -type f -exec chmod +x {} +
|
||||
|
||||
CMD [ "/app/bin/livebook", "start" ]
|
72
README.md
72
README.md
|
@ -3,7 +3,7 @@
|
|||
Livebook is a web application for writing interactive and collaborative code notebooks. It features:
|
||||
|
||||
* A deployable web app built with [Phoenix LiveView](https://github.com/phoenixframework/phoenix_live_view) where users can create, fork, and run multiple notebooks.
|
||||
|
||||
|
||||
* Each notebook is made of multiple sections: each section is made of Markdown and Elixir cells. Code in Elixir cells can be evaluated on demand. Mathematical formulas are also supported via [KaTeX](https://katex.org/).
|
||||
|
||||
* Persistence: notebooks can be persisted to disk through the `.livemd` format, which is a subset of Markdown. This means your notebooks can be saved for later, easily shared, and they also play well with version control.
|
||||
|
@ -24,16 +24,63 @@ The current version provides only the initial step of our Livebook vision. Our p
|
|||
|
||||
## Usage
|
||||
|
||||
For now, the best way to run Livebook is by cloning it and running it locally:
|
||||
We provide several distinct methods of running Livebook,
|
||||
pick the one that best fits your use case.
|
||||
|
||||
$ git clone https://github.com/elixir-nx/livebook.git
|
||||
$ cd livebook
|
||||
$ mix deps.get --only prod
|
||||
$ MIX_ENV=prod mix phx.server
|
||||
### Mix
|
||||
|
||||
You can run latest Livebook directly with Mix.
|
||||
|
||||
```shell
|
||||
git clone https://github.com/elixir-nx/livebook.git
|
||||
cd livebook
|
||||
mix deps.get --only prod
|
||||
|
||||
# Run the Livebook server
|
||||
MIX_ENV=prod mix phx.server
|
||||
```
|
||||
|
||||
You will need [Elixir v1.11](https://elixir-lang.org/install.html) or later.
|
||||
|
||||
We will work on other distribution modes (escripts, Docker images, etc) once we start distributing official releases.
|
||||
### Escript
|
||||
|
||||
Running Livebook using Escript makes for a very convenient option
|
||||
for local usage and provides easy configuration via CLI options.
|
||||
|
||||
```shell
|
||||
# Currently you need to build the Escript manually,
|
||||
# we will publish it to Hex once we release the first version
|
||||
git clone https://github.com/elixir-nx/livebook.git
|
||||
cd livebook
|
||||
mix deps.get --only prod
|
||||
MIX_ENV=prod mix escript.build
|
||||
|
||||
# Start the Livebook server
|
||||
./livebook server
|
||||
|
||||
# See all the configuration options
|
||||
./livebook server --help
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
Running Livebook using Docker is a great option for cloud deployments
|
||||
and also for local usage in case you don't have Elixir installed.
|
||||
|
||||
```shell
|
||||
# Running with the default configuration
|
||||
docker run -p 8080:8080 livebook/livebook
|
||||
|
||||
# In order to access and save notebooks directly to your machine
|
||||
# you can mount a local directory into the container.
|
||||
# Make sure to specify the user with "-u $(id -u):$(id -g)"
|
||||
# so that the created files have proper permissions
|
||||
docker run -p 8080:8080 -u $(id -u):$(id -g) -v <LOCAL_DIR>:/data livebook/livebook
|
||||
|
||||
# You can configure Livebook using environment variables,
|
||||
# for all options see the dedicated "Environment variables" section below
|
||||
docker run -p 8080:8080 -e LIVEBOOK_PASSWORD="securesecret" livebook/livebook
|
||||
```
|
||||
|
||||
### Security considerations
|
||||
|
||||
|
@ -46,13 +93,18 @@ For this reason, Livebook only binds to the 127.0.0.1, allowing access to happen
|
|||
|
||||
The following environment variables configure Livebook:
|
||||
|
||||
* LIVEBOOK_COOKIE - sets the cookie for running Livebook in a cluster.
|
||||
Defaults to a random string that is generated on boot.
|
||||
|
||||
* LIVEBOOK_IP - sets the ip address to start the web application on. Must be a valid IPv4 or IPv6 address.
|
||||
|
||||
* LIVEBOOK_PASSWORD - sets a password that must be used to access Livebook. Must be at least 12 characters. Defaults to token authentication.
|
||||
|
||||
* LIVEBOOK_PORT - sets the port Livebook runs on. If you want multiple instances to run on the same domain but different ports, you also need to set `LIVEBOOK_SECRET_KEY_BASE`. Defaults to 8080.
|
||||
* LIVEBOOK_PORT - sets the port Livebook runs on. If you want multiple instances to run on the same domain but different ports, you also need to set 'LIVEBOOK_SECRET_KEY_BASE'. Defaults to 8080.
|
||||
|
||||
* LIVEBOOK_SECRET_KEY_BASE - sets a secret key that is used to sign and encrypt the session and other payloads used by Livebook. Must be at least 64 characters long and it can be generated by commands such as: `openssl rand -base64 48`. Defaults to a random secret on every boot.
|
||||
* LIVEBOOK_ROOT_PATH - sets the root path to use for file selection.
|
||||
|
||||
* LIVEBOOK_ROOT_PATH - the root path to use for file selection.
|
||||
* LIVEBOOK_SECRET_KEY_BASE - sets a secret key that is used to sign and encrypt the session and other payloads used by Livebook. Must be at least 64 characters long and it can be generated by commands such as: 'openssl rand -base64 48'. Defaults to a random secret on every boot.
|
||||
|
||||
<!-- Environment variables -->
|
||||
## License
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import Config
|
||||
|
||||
# Default bind and port for production
|
||||
config :livebook, LivebookWeb.Endpoint, http: [ip: {127, 0, 0, 1}, port: 8080]
|
||||
config :livebook, LivebookWeb.Endpoint,
|
||||
http: [ip: {127, 0, 0, 1}, port: 8080],
|
||||
server: true
|
||||
|
||||
# Start log-level in notice by default to reduce output
|
||||
config :logger, level: :notice
|
||||
|
|
|
@ -17,3 +17,11 @@ end
|
|||
if port = Livebook.Config.port!("LIVEBOOK_PORT") do
|
||||
config :livebook, LivebookWeb.Endpoint, http: [port: port]
|
||||
end
|
||||
|
||||
if ip = Livebook.Config.ip!("LIVEBOOK_IP") do
|
||||
config :livebook, LivebookWeb.Endpoint, http: [ip: ip]
|
||||
end
|
||||
|
||||
config :livebook,
|
||||
:cookie,
|
||||
Livebook.Config.cookie!("LIVEBOOK_COOKIE") || Livebook.Utils.random_cookie()
|
||||
|
|
|
@ -7,6 +7,7 @@ defmodule Livebook.Application do
|
|||
|
||||
def start(_type, _args) do
|
||||
ensure_distribution!()
|
||||
set_cookie()
|
||||
|
||||
children = [
|
||||
# Start the Telemetry supervisor
|
||||
|
@ -69,6 +70,11 @@ defmodule Livebook.Application do
|
|||
end
|
||||
end
|
||||
|
||||
defp set_cookie() do
|
||||
cookie = Application.fetch_env!(:livebook, :cookie)
|
||||
Node.set_cookie(cookie)
|
||||
end
|
||||
|
||||
defp get_node_type_and_name() do
|
||||
Application.get_env(:livebook, :node) || {:shortnames, random_short_name()}
|
||||
end
|
||||
|
|
|
@ -84,6 +84,37 @@ defmodule Livebook.Config do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parses and validates the ip from env.
|
||||
"""
|
||||
def ip!(env) do
|
||||
if ip = System.get_env(env) do
|
||||
ip!(env, ip)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parses and validates the ip within context.
|
||||
"""
|
||||
def ip!(context, ip) do
|
||||
case ip |> String.to_charlist() |> :inet.parse_address() do
|
||||
{:ok, ip} ->
|
||||
ip
|
||||
|
||||
{:error, :einval} ->
|
||||
abort!("expected #{context} to be a valid ipv4 or ipv6 address, got: #{ip}")
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parses the cookie from env.
|
||||
"""
|
||||
def cookie!(env) do
|
||||
if cookie = System.get_env(env) do
|
||||
String.to_atom(cookie)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parses and validates the password from env.
|
||||
"""
|
||||
|
|
|
@ -19,6 +19,14 @@ defmodule Livebook.Utils do
|
|||
:crypto.strong_rand_bytes(5) |> Base.encode32(case: :lower)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generates a random cookie for a distributed node.
|
||||
"""
|
||||
@spec random_cookie() :: atom()
|
||||
def random_cookie() do
|
||||
:crypto.strong_rand_bytes(42) |> Base.url_encode64() |> String.to_atom()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given name to node identifier.
|
||||
"""
|
||||
|
|
|
@ -2,6 +2,7 @@ defmodule LivebookCLI.Server do
|
|||
@moduledoc false
|
||||
|
||||
@behaviour LivebookCLI.Task
|
||||
|
||||
@external_resource "README.md"
|
||||
|
||||
[_, environment_variables, _] =
|
||||
|
@ -9,7 +10,6 @@ defmodule LivebookCLI.Server do
|
|||
|> File.read!()
|
||||
|> String.split("<!-- Environment variables -->")
|
||||
|
||||
@external_resource "README.md"
|
||||
@environment_variables String.trim(environment_variables)
|
||||
|
||||
@impl true
|
||||
|
@ -19,12 +19,15 @@ defmodule LivebookCLI.Server do
|
|||
|
||||
Available options:
|
||||
|
||||
--cookie Sets a cookie for the app distributed node
|
||||
--ip The ip address to start the web application on, defaults to 127.0.0.1
|
||||
Must be a valid IPv4 or IPv6 address
|
||||
--name Set a name for the app distributed node
|
||||
--no-token Disable token authentication, enabled by default
|
||||
If LIVEBOOK_PASSWORD is set, it takes precedence over token auth
|
||||
--sname Set a short name for the app distributed node
|
||||
-p, --port The port to start the web application on, defaults to 8080
|
||||
--root-path The root path to use for file selection
|
||||
--sname Set a short name for the app distributed node
|
||||
|
||||
The --help option can be given to print this notice.
|
||||
|
||||
|
@ -62,8 +65,8 @@ defmodule LivebookCLI.Server do
|
|||
end
|
||||
|
||||
defp start_server() do
|
||||
Application.put_env(:phoenix, :serve_endpoints, true, persistent: true)
|
||||
|
||||
# We configure the endpoint with `server: true`,
|
||||
# so it's gonna start listening
|
||||
case Application.ensure_all_started(:livebook) do
|
||||
{:ok, _} -> :ok
|
||||
{:error, _} -> :error
|
||||
|
@ -71,11 +74,13 @@ defmodule LivebookCLI.Server do
|
|||
end
|
||||
|
||||
@switches [
|
||||
token: :boolean,
|
||||
port: :integer,
|
||||
cookie: :string,
|
||||
ip: :string,
|
||||
name: :string,
|
||||
port: :integer,
|
||||
root_path: :string,
|
||||
sname: :string,
|
||||
root_path: :string
|
||||
token: :boolean
|
||||
]
|
||||
|
||||
@aliases [
|
||||
|
@ -108,6 +113,11 @@ defmodule LivebookCLI.Server do
|
|||
opts_to_config(opts, [{:livebook, LivebookWeb.Endpoint, http: [port: port]} | config])
|
||||
end
|
||||
|
||||
defp opts_to_config([{:ip, ip} | opts], config) do
|
||||
ip = Livebook.Config.ip!("--ip", ip)
|
||||
opts_to_config(opts, [{:livebook, LivebookWeb.Endpoint, http: [ip: ip]} | config])
|
||||
end
|
||||
|
||||
defp opts_to_config([{:root_path, root_path} | opts], config) do
|
||||
root_path = Livebook.Config.root_path!("--root-path", root_path)
|
||||
opts_to_config(opts, [{:livebook, :root_path, root_path} | config])
|
||||
|
@ -123,5 +133,10 @@ defmodule LivebookCLI.Server do
|
|||
opts_to_config(opts, [{:livebook, :node, {:longnames, name}} | config])
|
||||
end
|
||||
|
||||
defp opts_to_config([{:cookie, cookie} | opts], config) do
|
||||
cookie = String.to_atom(cookie)
|
||||
opts_to_config(opts, [{:livebook, :cookie, cookie} | config])
|
||||
end
|
||||
|
||||
defp opts_to_config([_opt | opts], config), do: opts_to_config(opts, config)
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<div class="text-2xl text-gray-50 w-full pt-2">
|
||||
<form method="post" class="flex flex-col space-y-4 items-center">
|
||||
<input type="hidden" value="<%= Phoenix.Controller.get_csrf_token() %>" name="_csrf_token"/>
|
||||
<input type="password" name="password" class="input" placeholder="Password" />
|
||||
<input type="password" name="password" class="input" placeholder="Password" autofocus />
|
||||
<button type="submit" class="button button-blue">
|
||||
Authenticate
|
||||
</button>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
<div class="text-2xl text-gray-50 w-full pt-2">
|
||||
<form method="get" class="flex flex-col space-y-4 items-center">
|
||||
<input type="text" name="token" class="input" placeholder="Token" />
|
||||
<input type="text" name="token" class="input" placeholder="Token" autofocus />
|
||||
<button type="submit" class="button button-blue">
|
||||
Authenticate
|
||||
</button>
|
||||
|
|
12
mix.exs
12
mix.exs
|
@ -11,7 +11,8 @@ defmodule Livebook.MixProject do
|
|||
start_permanent: Mix.env() == :prod,
|
||||
aliases: aliases(),
|
||||
deps: deps(),
|
||||
escript: escript()
|
||||
escript: escript(),
|
||||
releases: releases()
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -57,4 +58,13 @@ defmodule Livebook.MixProject do
|
|||
app: nil
|
||||
]
|
||||
end
|
||||
|
||||
defp releases() do
|
||||
[
|
||||
livebook: [
|
||||
include_executables_for: [:unix],
|
||||
include_erts: false
|
||||
]
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue