feat(elixir): add docker deployment
This commit is contained in:
48
.dockerignore
Normal file
48
.dockerignore
Normal file
@@ -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
|
||||
99
Dockerfile
Normal file
99
Dockerfile
Normal file
@@ -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"]
|
||||
124
README.md
124
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 <repository-url>
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
34
docker-compose.yml
Normal file
34
docker-compose.yml
Normal file
@@ -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:
|
||||
30
lib/components_elixir/release.ex
Normal file
30
lib/components_elixir/release.ex
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user