# ludops-todo A fullstack monorepo template for quickly scaffolding and deploying apps on your own infrastructure. **Stack:** Node.js (Fastify) · React + Vite · PostgreSQL (Drizzle ORM) · Docker · Gitea CI/CD · Nginx Proxy Manager --- ## Creating a new app from this template ### 1. Fork or copy the repository In Gitea, use **"Fork"** or create a new repo and push this one under a new name. Convention: `ludops-` (e.g. `ludops-portfolio`, `ludops-dashboard`). ### 2. Rename everything (global find & replace) Search the entire repo for `todo` and replace with your app name (lowercase, no spaces). | File | What changes | |---|---| | `.env.example` | `APP_NAME` and `REGISTRY_IMAGE` values | | Root `package.json` | `"name"` field | | `apps/web/src/App.tsx` | `APP_NAME` constant (display name) | > **Tip:** In VS Code use `Ctrl+Shift+H` to find & replace across all files. ### 3. Set repository variables in Gitea In your new repo: **Settings → Variables → Add variable** | Variable | Example value | |---|---| | `APP_NAME` | `myapp` | | `REGISTRY_IMAGE` | `git.ludops.com/ludops/ludops-myapp` | These are injected into the CI runner automatically — no `.env` file needed on the server for the pipeline. ### 4. Create the database Connect to your shared Postgres instance and create the app's database: ```sh sudo docker exec -it ludops-postgres psql -U gitea -c "CREATE DATABASE todo_db;" ``` The `DATABASE_URL` in `docker-compose.prod.yml` automatically uses `${APP_NAME}_db`. ### 5. Push to trigger the CI pipeline ```bash git push ``` Gitea Actions will: 1. Build and push the Docker image to the registry 2. Pull the new image on the server 3. Run `drizzle-kit push` to sync the DB schema 4. Start the container with `docker compose up -d` ### 6. Route your domain In **Nginx Proxy Manager**, add a new proxy host: - **Domain:** `myapp.yourdomain.com` - **Forward hostname:** `ludops-myapp-app` ← matches `container_name` in docker-compose - **Forward port:** `3000` - Enable **SSL** via Let's Encrypt That's it. Your app is live. --- ## Project structure ``` . ├── apps/ │ ├── api/ # Fastify backend │ │ ├── src/ │ │ │ ├── index.ts # Server entry point — add your API routes here │ │ │ └── db/ │ │ │ ├── index.ts # Drizzle client │ │ │ ├── schemas.ts │ │ │ └── schemas/ │ │ │ └── visit-logs.schema.ts # Keep: tracks page visits │ │ └── drizzle.config.ts │ └── web/ # React + Vite frontend │ └── src/ │ └── App.tsx # Start building your UI here ├── packages/ │ └── shared/ # Types shared between api and web │ └── index.ts ├── docker-compose.prod.yml # Production container config ├── Dockerfile # Multi-stage build ├── .env.example # ← copy to .env on the server └── .gitea/workflows/ └── deploy.yml # CI/CD pipeline ``` --- ## Local development ```bash # Install dependencies pnpm install # Start both api and web in watch mode pnpm dev ``` - Web: http://localhost:5173 - API: http://localhost:3000 You'll need a local Postgres instance and a `.env` or the `DATABASE_URL` env var set for the API. --- ## Adding database tables 1. Create a new schema file in `apps/api/src/db/schemas/` 2. Export it from `apps/api/src/db/schemas.ts` 3. Push to the server — the CI pipeline runs `drizzle-kit push` automatically --- ## Adding shared types Add exports to `packages/shared/index.ts`. They are available in both `api` and `web` as `@ludops/shared`. --- ## Built-in features kept from the todo | Feature | Where | |---|---| | Visit logging (IP + user agent per API request) | `apps/api/src/db/schemas/visit-logs.schema.ts` | | `/api/status` endpoint (health + visit count) | `apps/api/src/index.ts` | | SPA fallback routing | `apps/api/src/index.ts` | | Static file serving (web dist) | `apps/api/src/index.ts` |