diff --git a/.env.docker.example b/.env.docker.example new file mode 100644 index 0000000..a38f5f4 --- /dev/null +++ b/.env.docker.example @@ -0,0 +1,23 @@ +# Copy to .env, fill the secrets, then: docker compose up --build +# Generate each secret with: openssl rand -base64 32 + +NEXT_PUBLIC_APP_NAME=Align + +# ── Auth (REQUIRED — generate your own, do not ship these) ────────────── +AUTH_SECRET=replace-with-openssl-rand-base64-32 +BETTER_AUTH_SECRET=replace-with-openssl-rand-base64-32 +TOTP_ENC_KEY=replace-with-openssl-rand-base64-32 +BETTER_AUTH_URL=http://localhost:3000 +NEXTAUTH_URL=http://localhost:3000 + +# ── First-run seed ────────────────────────────────────────────────────── +# SEED_ON_START=true creates default categories and one admin user below. +SEED_ON_START=true +USER1_EMAIL=admin@example.com +USER1_PASSWORD=changeme123 +USER1_NAME=Admin + +# ── Postgres (defaults are fine for local) ────────────────────────────── +POSTGRES_USER=align +POSTGRES_PASSWORD=align +POSTGRES_DB=align_dev diff --git a/README.md b/README.md index 07b9448..8ed2b47 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,19 @@ Longer version in [apps/web/README.md](apps/web/README.md). Deploying to Vercel Want to call it something other than "Align"? Set `NEXT_PUBLIC_APP_NAME` and it changes everywhere in the UI. +### Or with Docker + +Brings up Postgres and the web app together — no local Node or Postgres needed: + +```bash +cp .env.docker.example .env # then set the three secrets it lists +docker compose up --build # http://localhost:3000 +``` + +The database starts first, migrations run automatically, and (with +`SEED_ON_START=true`) a first admin user is created from the `USER1_*` values in +your `.env`. Postgres is exposed on `localhost:5434` if you want to poke at it. + ## Running the mobile app ```bash diff --git a/apps/web/.dockerignore b/apps/web/.dockerignore new file mode 100644 index 0000000..f53e72e --- /dev/null +++ b/apps/web/.dockerignore @@ -0,0 +1,12 @@ +node_modules +.next +.git +.vercel +coverage +.env +.env.* +!.env.example +Dockerfile +.dockerignore +npm-debug.log* +*.log diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile new file mode 100644 index 0000000..ec989de --- /dev/null +++ b/apps/web/Dockerfile @@ -0,0 +1,28 @@ +# syntax=docker/dockerfile:1 +# Self-contained image for the Align web app (Next.js 16 + Prisma + Postgres). +# Installs deps, generates the Prisma client and builds; at container start it +# applies migrations (and optionally seeds) then boots the production server. + +FROM node:20-alpine AS build +RUN corepack enable +WORKDIR /app + +# Install dependencies first against the committed lockfile for better caching. +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile + +# Build. A placeholder DATABASE_URL keeps `prisma generate` / `next build` +# happy — no database is contacted at build time; the real URL is injected at +# runtime by docker-compose. +COPY . . +ENV DATABASE_URL="postgresql://placeholder:placeholder@localhost:5432/placeholder" +ENV NEXT_TELEMETRY_DISABLED=1 +RUN pnpm prisma generate && pnpm build + +ENV NODE_ENV=production +ENV PORT=3000 +EXPOSE 3000 + +RUN chmod +x docker-entrypoint.sh +ENTRYPOINT ["./docker-entrypoint.sh"] +CMD ["pnpm", "start"] diff --git a/apps/web/docker-entrypoint.sh b/apps/web/docker-entrypoint.sh new file mode 100644 index 0000000..3573d6d --- /dev/null +++ b/apps/web/docker-entrypoint.sh @@ -0,0 +1,24 @@ +#!/bin/sh +set -e + +# Apply migrations, retrying while Postgres finishes coming up. +echo "▶ Applying database migrations..." +n=0 +until pnpm prisma migrate deploy; do + n=$((n + 1)) + if [ "$n" -ge 10 ]; then + echo "✖ migrations failed after $n attempts" + exit 1 + fi + echo " database not ready — retry $n/10 in 3s..." + sleep 3 +done + +# Optional first-run seed (categories + an admin user from USER1_* env vars). +if [ "${SEED_ON_START:-false}" = "true" ]; then + echo "▶ Seeding database..." + pnpm exec tsx prisma/seed.ts || echo "⚠ seed failed or already applied — continuing" +fi + +echo "▶ Starting Align on :${PORT:-3000}" +exec "$@" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..807eb87 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,58 @@ +# Run the full Align web app locally (contributors / self-hosting): +# +# cp .env.docker.example .env +# # edit .env — set AUTH_SECRET / BETTER_AUTH_SECRET / TOTP_ENC_KEY +# docker compose up --build +# +# App: http://localhost:3000 +# Postgres: localhost:5434 (user/pass/db: align / align / align_dev) +# +# The db service alone (without web) also backs local `next dev` — the app's +# prisma.ts uses the plain pg driver for any non-Neon host, so this Postgres +# works directly. + +services: + db: + image: postgres:17 + container_name: align-db + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER:-align} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-align} + POSTGRES_DB: ${POSTGRES_DB:-align_dev} + ports: + - "5434:5432" + volumes: + - align-db-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-align} -d ${POSTGRES_DB:-align_dev}"] + interval: 5s + timeout: 5s + retries: 10 + + web: + build: + context: ./apps/web + container_name: align-web + restart: unless-stopped + depends_on: + db: + condition: service_healthy + environment: + DATABASE_URL: postgresql://${POSTGRES_USER:-align}:${POSTGRES_PASSWORD:-align}@db:5432/${POSTGRES_DB:-align_dev} + NEXT_PUBLIC_APP_NAME: ${NEXT_PUBLIC_APP_NAME:-Align} + AUTH_SECRET: ${AUTH_SECRET:?set AUTH_SECRET in .env (openssl rand -base64 32)} + BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?set BETTER_AUTH_SECRET in .env (openssl rand -base64 32)} + BETTER_AUTH_URL: ${BETTER_AUTH_URL:-http://localhost:3000} + NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3000} + TOTP_ENC_KEY: ${TOTP_ENC_KEY:?set TOTP_ENC_KEY in .env (openssl rand -base64 32)} + # First-run seed: categories + an admin user from the USER1_* values. + SEED_ON_START: ${SEED_ON_START:-true} + USER1_EMAIL: ${USER1_EMAIL:-admin@example.com} + USER1_PASSWORD: ${USER1_PASSWORD:-changeme123} + USER1_NAME: ${USER1_NAME:-Admin} + ports: + - "3000:3000" + +volumes: + align-db-data: