ludops-skeleton/README.md

148 lines
4.0 KiB
Markdown

# ludops-skeleton
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-<appname>` (e.g. `ludops-portfolio`, `ludops-dashboard`).
### 2. Rename everything (global find & replace)
Search the entire repo for `skeleton` 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. Create your `.env` file on the server
On the deployment server, in the project directory:
```bash
cp .env.example .env
nano .env
```
Set:
```env
APP_NAME=myapp
REGISTRY_IMAGE=git.ludops.com/ludops/ludops-myapp
```
This file is gitignored — it lives only on the server.
### 4. Create the database
Connect to your shared Postgres instance and create the app's database:
```sql
CREATE DATABASE myapp_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 skeleton
| 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` |