diff --git a/Dockerfile b/Dockerfile index e0feee3..14b2e75 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,80 +1,102 @@ -# Use Elixir official image +# ============================================ +# Stage 0: Common base (system dependencies) +# ============================================ FROM elixir:1.15.7-alpine AS base -# Install build dependencies RUN apk add --no-cache \ build-base \ git \ - postgresql-client \ - inotify-tools \ - curl \ - sudo - -# Create user with same UID/GID as host user -ARG USER_ID=1000 -ARG GROUP_ID=1000 -RUN addgroup -g ${GROUP_ID} -S elixir && \ - adduser -u ${USER_ID} -S elixir -G elixir -s /bin/sh - -# Set work directory + openssh-client \ + postgresql-dev \ + && rm -rf /var/cache/apk/* + WORKDIR /app +RUN mix do local.hex --force, local.rebar --force -# Change ownership -RUN chown -R elixir:elixir /app +# ============================================ +# Stage 1: Dependencies (better caching) +# ============================================ +FROM base AS deps -# Switch to elixir user -USER elixir +# Copy only dependency files first +COPY mix.exs mix.lock ./ +RUN --mount=type=ssh mix deps.get --only prod -# Install Hex and Rebar -RUN mix local.hex --force && \ - mix local.rebar --force +# ============================================ +# Stage 2: Development +# ============================================ +FROM base AS dev -############################################# -# Dependencies stage - for caching -############################################# -FROM base AS deps +# Add only what is specific for development +RUN apk add --no-cache nodejs npm inotify-tools \ + && rm -rf /var/cache/apk/* + +COPY mix.exs mix.lock ./ +COPY config config +RUN --mount=type=ssh mix deps.get + +# The rest will be set up via volume in Compose -# Copy mix files first for better caching -COPY --chown=elixir:elixir mix.exs mix.lock ./ +# ============================================ +# Stage 3: Build (production) +# ============================================ +FROM base AS build -# Install dependencies -RUN mix deps.get +# Add only build-specific files +RUN apk add --no-cache nodejs npm \ + && rm -rf /var/cache/apk/* -############################################# -# Development stage -############################################# -FROM deps AS development +ARG mix_env=prod +WORKDIR /app + +# 1. Dependencies first (better caching) +COPY mix.exs mix.lock ./ +RUN --mount=type=ssh mix deps.get --only $mix_env -# Copy scripts first (they change less frequently) -COPY --chown=elixir:elixir scripts/ ./scripts/ +# 2. Configurations +COPY config config -# Make ALL scripts executable - FIX AQUI -RUN find ./scripts -type f -name "*.sh" -exec chmod +x {} \; +# 3. Source code +COPY lib lib +COPY priv priv +COPY scripts scripts -# Copy rest of application code -COPY --chown=elixir:elixir . . +# 4. Assets +COPY assets assets +RUN if [ -f "assets/package.json" ]; then \ + cd assets && npm ci --omit=dev && \ + (npm run deploy || npm run build || true); \ + fi -# Compile dependencies only (code will be compiled by script) -RUN mix deps.compile +# 5. Compile and make release +RUN MIX_ENV=$mix_env mix compile +RUN MIX_ENV=$mix_env mix release -# Default command will be overridden by docker-compose -CMD ["mix", "phx.server"] +# ============================================ +# Stage 4: Runtime (production – minimal image) +# ============================================ +FROM alpine:3.20 AS app -############################################# -# Production stage (for future use) -############################################# -FROM deps AS production +RUN apk add --no-cache \ + openssl \ + ncurses-libs \ + libstdc++ \ + ca-certificates \ + && rm -rf /var/cache/apk/* + +ARG mix_env=prod +WORKDIR /app -ENV MIX_ENV=prod +RUN addgroup -g 1000 -S appuser && \ + adduser -u 1000 -S appuser -G appuser && \ + chown -R appuser:appuser /app -# Copy application code -COPY --chown=elixir:elixir . . +USER appuser:appuser -# Compile application -RUN mix deps.compile && \ - mix compile +COPY --from=build --chown=appuser:appuser /app/_build/${mix_env}/rel/sendwise ./ +COPY --from=build --chown=appuser:appuser /app/scripts/start.sh ./start.sh -# Create release -RUN mix release +ENV HOME=/app \ + PATH=/app/bin:$PATH -CMD ["_build/prod/rel/foedus/bin/foedus", "start"] \ No newline at end of file +CMD ["sh", "./start.sh"] diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..b6f7edc --- /dev/null +++ b/compose.yml @@ -0,0 +1,53 @@ +services: + foedus: + build: + context: . + target: dev + restart: unless-stopped + container_name: foedus + command: mix phx.server + stdin_open: true + tty: true + depends_on: + foedus_db: + condition: service_healthy + ports: + - 4000:4000 + volumes: + - .:/app + - foedus_deps:/app/deps + - foedus_build:/app/_build + env_file: + - .env + environment: + - PHX_LIVE_RELOAD_POLLING=true + networks: + - foedus_net + + foedus_db: + image: "postgres:17" + container_name: foedus_db + environment: + POSTGRES_USER: ${DATABASE_USER} + POSTGRES_PASSWORD: ${DATABASE_PASSWORD} + ports: + - "5432:5432" + env_file: + - .env + volumes: + - "foedus_db:/var/lib/postgresql/data" + networks: + - foedus_net + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + +networks: + foedus_net: + +volumes: + foedus_db: + foedus_deps: + foedus_build: diff --git a/config/dev.exs b/config/dev.exs index d94ac20..d58232e 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -2,10 +2,10 @@ import Config # Configure your database config :foedus, Foedus.Repo, - username: "postgres", - password: "postgres", - hostname: "db", - database: "foedus_dev", + username: System.get_env("DATABASE_USER") || "postgres", + password: System.get_env("DATABASE_PASSWORD") || "postgres", + hostname: System.get_env("DATABASE_HOST") || "my_doctor_db", + database: System.get_env("DATABASE_NAME") || "my_doctor_dev", stacktrace: true, show_sensitive_data_on_connection_error: true, pool_size: 10 diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 15c0df4..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,58 +0,0 @@ -services: - db: - image: postgres:13 - container_name: foedus_postgres - environment: - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_DB: ${POSTGRES_DB} - PGDATA: /var/lib/postgresql/data/pgdata - volumes: - - postgres_data:/var/lib/postgresql/data - ports: - - "${POSTGRES_PORT}:5432" - restart: unless-stopped - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] - interval: 30s - timeout: 10s - retries: 3 - networks: - - foedus_network - - app: - build: - context: . - target: development - container_name: foedus_app - depends_on: - db: - condition: service_healthy - environment: - MIX_ENV: dev - DATABASE_URL: ${DATABASE_URL} - SECRET_KEY_BASE: ${SECRET_KEY_BASE} - PHX_HOST: ${PHX_HOST} - ports: - - "${PHX_PORT}:4000" - volumes: - - .:/app - - /app/deps - - /app/_build - - deps_cache:/app/deps - - build_cache:/app/_build - restart: unless-stopped - networks: - - foedus_network - stdin_open: true - tty: true - command: ["/app/scripts/dev-start.sh"] - -networks: - foedus_network: - driver: bridge - -volumes: - postgres_data: - deps_cache: - build_cache: diff --git a/lib/release.ex b/lib/release.ex new file mode 100644 index 0000000..11c0a15 --- /dev/null +++ b/lib/release.ex @@ -0,0 +1,28 @@ +defmodule Foedus.Release do + @moduledoc """ + Used for executing DB release tasks when run in production without Mix + installed. + """ + @app :sendwise + + 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 + Application.load(@app) + end +end diff --git a/scripts/dev-start.sh b/scripts/dev-start.sh deleted file mode 100755 index f457ed0..0000000 --- a/scripts/dev-start.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -set -e - -echo "🚀 Starting Foedus development server..." - -# Verifica se as dependências precisam ser instaladas -if [ ! -d "deps" ] || [ "mix.lock" -nt "deps/.mix_deps_timestamp" ]; then - echo "📦 Installing/updating dependencies..." - mix deps.get - touch deps/.mix_deps_timestamp -fi - -# Verifica se precisa compilar -if [ ! -d "_build" ] || find lib config -newer _build/dev -print -quit | grep -q .; then - echo "🔨 Compiling application..." - mix compile -fi - -# Setup do banco (apenas se necessário) -echo "🗃️ Setting up database..." -mix ecto.create --quiet || true -mix ecto.migrate --quiet || true - -echo "✅ Starting Phoenix server..." -exec mix phx.server \ No newline at end of file diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100755 index 0000000..2f4f186 --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +if [ -f "bin/foedus" ]; then + bin/foedus eval "Foedus.Release.migrate" || true + bin/foedus start +else + exec "$@" +fi