From 280caad6410465055ee475ebbcc210128c3190ee Mon Sep 17 00:00:00 2001 From: Schuwi Date: Sun, 14 Sep 2025 21:01:04 +0200 Subject: [PATCH] feat(elixir): add docker deployment --- .dockerignore | 48 ++++++++++++ Dockerfile | 99 ++++++++++++++++++++++++ README.md | 124 ++++++++++++++++++++++++++++++- assets/js/app.js | 3 +- docker-compose.yml | 34 +++++++++ lib/components_elixir/release.ex | 30 ++++++++ 6 files changed, 335 insertions(+), 3 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 lib/components_elixir/release.ex diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..09859c1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,48 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +components_elixir-*.tar + +# Temporary files, for example, from tests. +/tmp/ + +# If NPM crashes, it generates a log, let's ignore it too. +npm-debug.log + +# The directory NPM downloads your dependencies sources to. +/assets/node_modules/ + +# Since we are building assets from assets/, +# we ignore priv/static. You may want to comment +# this depending on your deployment strategy. +/priv/static/ + +# Files matching config/*.secret.exs pattern contains sensitive +# data and you should not commit them into version control. +# +# Alternatively, you may comment the line below and commit the +# secrets files as long as you replace their contents by environment +# variables. +/config/*.secret.exs + +.elixir_ls/ +.DS_Store \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..71ad5ff --- /dev/null +++ b/Dockerfile @@ -0,0 +1,99 @@ +# Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian +# instead of Alpine to avoid DNS resolution issues in production. +# +# https://hub.docker.com/r/hexpm/elixir/tags?page=1&name=ubuntu +# https://hub.docker.com/_/ubuntu?tab=tags +# +# This file is based on these images: +# +# - https://hub.docker.com/r/hexpm/elixir/tags - for the build image +# - https://hub.docker.com/_/debian?tab=tags&page=1&name=bookworm - for the release image +# - https://pkgs.org/ - resource for finding needed packages +# +# Use more recent and available images +ARG ELIXIR_VERSION=1.15 +ARG OTP_VERSION=26 +ARG DEBIAN_VERSION=bookworm-slim + +ARG BUILDER_IMAGE="elixir:${ELIXIR_VERSION}-otp-${OTP_VERSION}" +ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" + +FROM ${BUILDER_IMAGE} as builder + +# install build dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential git ca-certificates curl \ + && update-ca-certificates && rm -rf /var/lib/apt/lists/* + +# prepare build dir +WORKDIR /app + +# install hex + rebar +RUN mix local.hex --force && \ + mix local.rebar --force + +# set build ENV +ENV MIX_ENV="prod" + +# install mix dependencies +COPY mix.exs mix.lock ./ +RUN mix deps.get --only $MIX_ENV +RUN mkdir config + +# copy compile-time config files before we compile dependencies +# to ensure any relevant config change will trigger the dependencies +# to be re-compiled. +COPY config/config.exs config/${MIX_ENV}.exs config/ +RUN mix deps.compile + +# Pre-install Tailwind and ESBuild to avoid network issues during assets.deploy +RUN mix tailwind.install --if-missing +RUN mix esbuild.install --if-missing + +COPY priv priv + +COPY lib lib + +COPY assets assets + +# Copy the apriltags.ps file required for compilation +COPY apriltags.ps . + +# compile assets +RUN mix assets.deploy + +# Compile the release +RUN mix compile + +# Changes to config/runtime.exs don't require recompiling the code +COPY config/runtime.exs config/ + +COPY rel rel +RUN mix release + +# start a new build stage so that the final image will only contain +# the compiled release and other runtime necessities +FROM ${RUNNER_IMAGE} + +RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales \ + && apt-get clean && rm -f /var/lib/apt/lists/*_* + +# Set the locale +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen + +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +WORKDIR "/app" +RUN chown nobody /app + +# set runner ENV +ENV MIX_ENV="prod" + +# Only copy the final release from the build stage +COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/components_elixir ./ + +USER nobody + +CMD ["/app/bin/server"] \ No newline at end of file diff --git a/README.md b/README.md index 7a59b35..7bef1f2 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,129 @@ mix ecto.migrations ## Deployment -For production deployment: +### 🐳 Docker Deployment (Recommended) + +#### Prerequisites +- Docker and Docker Compose installed +- Git for cloning the repository + +#### Quick Start + +1. **Clone the repository:** + ```bash + git clone + cd components_elixir + ``` + +2. **Build and run with Docker Compose:** + ```bash + docker compose up --build + ``` + + **⚠️ Build Time Notice**: The initial build can take 5-15 minutes because: + - Tailwind CSS downloads ~500MB from GitHub (can be very slow) + - ESBuild downloads additional build tools + - Elixir compiles all dependencies + - Network conditions may significantly affect download speeds + +3. **Access the application:** + - Open [http://localhost:4000](http://localhost:4000) + - Default password: `changeme` + +#### Docker Configuration Files + +The project includes these Docker files: + +- **`Dockerfile`** - Multi-stage build for production +- **`docker-compose.yml`** - Local development/testing setup +- **`.dockerignore`** - Excludes unnecessary files from build context + +#### Customizing Docker Deployment + +1. **Environment Variables**: Edit `docker-compose.yml` to customize: + ```yaml + environment: + DATABASE_URL: "ecto://postgres:postgres@db:5432/components_elixir_prod" + SECRET_KEY_BASE: "your-secret-key-here" # Generate with: mix phx.gen.secret + PHX_HOST: "localhost" # Change to your domain + PHX_SERVER: "true" + PORT: "4000" + ``` + +2. **Generate a secure secret key:** + ```bash + # Run this locally to generate a new secret + mix phx.gen.secret + ``` + +3. **Database Configuration**: The default setup includes: + - PostgreSQL 15 container + - Automatic database creation + - Health checks to ensure proper startup order + - Persistent data storage with Docker volumes + +#### Production Docker Deployment + +For production environments: + +1. **Create a production docker-compose.yml:** + ```yaml + services: + db: + image: postgres:15 + environment: + POSTGRES_USER: components_user + POSTGRES_PASSWORD: secure_db_password + POSTGRES_DB: components_elixir_prod + volumes: + - postgres_data:/var/lib/postgresql/data + restart: unless-stopped + + app: + build: . + ports: + - "80:4000" + environment: + DATABASE_URL: "ecto://components_user:secure_db_password@db:5432/components_elixir_prod" + SECRET_KEY_BASE: "your-64-char-secret-key" + PHX_HOST: "yourdomain.com" + PHX_SERVER: "true" + PORT: "4000" + depends_on: + db: + condition: service_healthy + restart: unless-stopped + command: ["/bin/sh", "-c", "/app/bin/migrate && /app/bin/server"] + + volumes: + postgres_data: + ``` + +2. **Deploy to production:** + ```bash + docker compose -f docker-compose.prod.yml up -d + ``` + +#### Docker Troubleshooting + +**Build Issues:** +- **Slow Tailwind download**: This is normal - GitHub releases can be slow +- **Network timeouts**: Retry the build with `docker compose up --build` +- **AprilTag compilation errors**: Ensure `apriltags.ps` file exists in project root + +**Runtime Issues:** +- **Database connection errors**: Wait for PostgreSQL health check to pass +- **Permission errors**: Check file ownership and Docker user permissions +- **Port conflicts**: Change the port mapping in docker-compose.yml + +**Performance:** +- **Slow startup**: First-time container startup includes database initialization +- **Memory usage**: Elixir/Phoenix applications typically use 50-200MB RAM +- **Storage**: PostgreSQL data persists in Docker volumes + +### 🚀 Traditional Deployment + +For production deployment without Docker: 1. **Set environment variables:** ```bash diff --git a/assets/js/app.js b/assets/js/app.js index cff6bf1..291c004 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -22,14 +22,13 @@ import "phoenix_html" // Establish Phoenix Socket and LiveView configuration. import {Socket} from "phoenix" import {LiveSocket} from "phoenix_live_view" -import {hooks as colocatedHooks} from "phoenix-colocated/components_elixir" import topbar from "../vendor/topbar" const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") const liveSocket = new LiveSocket("/live", Socket, { longPollFallbackMs: 2500, params: {_csrf_token: csrfToken}, - hooks: {...colocatedHooks}, + hooks: {}, }) // Show progress bar on live navigation and form submits diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2786e11 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,34 @@ +services: + db: + image: postgres:15 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: components_elixir_prod + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + + app: + build: . + ports: + - "4000:4000" + environment: + DATABASE_URL: "ecto://postgres:postgres@db:5432/components_elixir_prod" + SECRET_KEY_BASE: "WYxST6ZVqFdeXRfjP3MUAc+hNVCJ6LW/+aqwuP27Ab77R4yXFQlO2HsukrOzCVif" + PHX_HOST: "localhost" + PHX_SERVER: "true" + PORT: "4000" + depends_on: + db: + condition: service_healthy + command: ["/bin/sh", "-c", "/app/bin/migrate && /app/bin/server"] + +volumes: + postgres_data: \ No newline at end of file diff --git a/lib/components_elixir/release.ex b/lib/components_elixir/release.ex new file mode 100644 index 0000000..d8ca49b --- /dev/null +++ b/lib/components_elixir/release.ex @@ -0,0 +1,30 @@ +defmodule ComponentsElixir.Release do + @moduledoc """ + Used for executing DB release tasks when run in production without Mix + installed. + """ + @app :components_elixir + + def migrate do + load_app() + + for repo <- repos() do + {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) + end + end + + def rollback(repo, version) do + load_app() + {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version)) + end + + defp repos do + Application.fetch_env!(@app, :ecto_repos) + end + + defp load_app do + # Many platforms require SSL when connecting to the database + Application.ensure_all_started(:ssl) + Application.ensure_loaded(@app) + end +end