Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# dependencies (installed during Docker build)
node_modules

# compiled output (generated during Docker build)
dist
src/generated

# git
.git
.gitignore

# CI/CD
.github

# test files (not needed for production build)
test
coverage

# env files (do not bake secrets into the image!)
.env*
secrets/

# docs and config
*.md
Dockerfile
.dockerignore
docker-compose.yml

# git hooks and editor
.husky
.zed
.vscode

# OS artifacts
.DS_Store
Thumbs.db

# logs
logs
*.log

# temp
.tmp
.temp
12 changes: 12 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Arcjet
ARCJET_KEY=

# PostgreSQL DB
DATABASE_URL=

# Better Auth
BETTER_AUTH_SECRET=
BETTER_AUTH_URL=

# Port
PORT=8080
63 changes: 47 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,15 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
needs: changes
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && needs.changes.outputs.code == 'true'
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout
if: needs.changes.outputs.code == 'true'
uses: actions/checkout@v7

- name: Dependency Review
if: needs.changes.outputs.code == 'true'
uses: actions/dependency-review-action@v5.0.0
with:
fail-on-severity: moderate
Expand All @@ -60,75 +58,108 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 3
needs: changes
if: needs.changes.outputs.code == 'true'
steps:
- name: Checkout
if: needs.changes.outputs.code == 'true'
uses: actions/checkout@v7

- name: Setup Biome
if: needs.changes.outputs.code == 'true'
uses: biomejs/setup-biome@v2
with:
version: 2.5.1

- name: Run Biome CI
if: needs.changes.outputs.code == 'true'
run: biome ci

typecheck:
name: Typecheck
runs-on: ubuntu-latest
timeout-minutes: 5
needs: changes
if: needs.changes.outputs.code == 'true'
steps:
- name: Checkout
if: needs.changes.outputs.code == 'true'
uses: actions/checkout@v7

- name: Setup Bun
if: needs.changes.outputs.code == 'true'
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.14"
cache: true

- name: Install dependencies
if: needs.changes.outputs.code == 'true'
run: bun install --frozen-lockfile

- name: Generate Prisma client
if: needs.changes.outputs.code == 'true'
run: bunx prisma generate

- name: Run typecheck
if: needs.changes.outputs.code == 'true'
run: bun run typecheck

test:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 5
needs: changes
if: needs.changes.outputs.code == 'true'
steps:
- name: Checkout
if: needs.changes.outputs.code == 'true'
uses: actions/checkout@v7

- name: Setup Bun
if: needs.changes.outputs.code == 'true'
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.14"
cache: true

- name: Install dependencies
if: needs.changes.outputs.code == 'true'
run: bun install --frozen-lockfile

- name: Generate Prisma client
if: needs.changes.outputs.code == 'true'
run: bunx prisma generate

- name: Run tests
if: needs.changes.outputs.code == 'true'
run: bun run test

image:
name: Build & Push Image
runs-on: ubuntu-latest
timeout-minutes: 20
needs: [changes, lint, typecheck, test]
if: needs.changes.outputs.code == 'true'
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GHCR
if: github.ref_name == 'main' && github.event_name == 'push'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.ref_name == 'main' && github.event_name == 'push' }}
tags: ghcr.io/kenneth-loto/taskforge-api:latest
cache-from: type=gha,scope=${{ github.base_ref || github.ref_name }}
cache-to: type=gha,mode=max,scope=${{ github.base_ref || github.ref_name }}

deploy:
name: Deploy
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [changes, image]
if: github.ref_name == 'main' && github.event_name == 'push' && needs.changes.outputs.code == 'true'
steps:
- name: Trigger Render deploy
run: curl "${{ secrets.RENDER_DEPLOY_HOOK }}"
61 changes: 18 additions & 43 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,58 +1,33 @@
# compiled output
/dist
/node_modules
/build

# IDE
/.vscode
/.idea
*.code-workspace

# OS
.DS_Store

# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# OS
.DS_Store

# Tests
/coverage
/.nyc_output

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# temp directory
.temp
.tmp

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# env files
.env*
!.env.example

# secrets (local Docker dev)
/secrets

# Prisma generated files
/src/generated

# temp
*.tsbuildinfo
91 changes: 91 additions & 0 deletions DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Deploy

## How it works

```
Push to main → GHA (lint → typecheck → test → image) → GHCR → Render deploy hook
```

The runner stage in the Dockerfile has **no secrets baked in** — secrets are mounted at runtime via Docker Compose secrets or Render environment variables.

## GitHub Secrets

Set these in **GitHub → Settings → Secrets and variables → Actions**:

| Secret | Required | Notes |
| -------------------- | -------- | -------------------------------------------- |
| `DATABASE_URL` | ✅ | PostgreSQL connection string |
| `BETTER_AUTH_SECRET` | ✅ | At least 32 chars, generated by `bunx auth secret` |
| `ARCJET_KEY` | ✅ | From Arcjet dashboard → site settings |
| `BETTER_AUTH_URL` | ✅ | Your Render app URL (e.g. `https://taskforge-api.onrender.com`) |
| `RENDER_DEPLOY_HOOK` | ✅ | From Render dashboard → Settings → Deploy Hook|

## Render Setup

### 1. Create the service

1. **Render Dashboard → New → Web Service**
2. **Source:** Select **Existing Image**
3. **Image:** `ghcr.io/kenneth-loto/taskforge-api:latest`
4. **Registry:** GitHub Container Registry (GHCR)
5. **Service Name:** `taskforge-api` (or your preference)

### 2. GHCR Credentials

Render needs a token to pull a private GHCR image:

1. GitHub → **Settings → Developer settings → Personal access tokens → Tokens (classic)**
2. Create a token with scope `read:packages`
3. Render → **Your Service → Settings → Registry Credentials**
4. Add:
- **Username:** `kenneth-loto`
- **Password:** (the PAT you just created)

**Alternatively**, make the package **public** after the first push:

- Go to **github.com → Packages → taskforge-api → Package settings → Change visibility → Public**

### 3. Environment Variables

Render Dashboard → **Your Service → Environment**:

| Variable | Masked |
| -------------------- | ------ |
| `DATABASE_URL` | ✅ |
| `BETTER_AUTH_SECRET` | ✅ |
| `ARCJET_KEY` | ✅ |
| `BETTER_AUTH_URL` | ✅ |
| `NODE_ENV` | No |

Set `NODE_ENV` to `production`.

### 4. Deploy Hook

1. Render Dashboard → **Your Service → Settings → Deploy Hook** → generate a hook URL
2. Copy it to GitHub → **Settings → Secrets and variables → Actions** as `RENDER_DEPLOY_HOOK`

## Trigger a deploy

Push to `main`:

```bash
git push origin main
```

Or trigger manually from **GitHub → Actions → CI → Run workflow** (select `main` branch).

## Health check

Once deployed, hit the root endpoint:

```bash
curl https://taskforge-api.onrender.com/
# {"statusCode":200,"message":"Success","data":{"message":"Welcome to the Taskforge API","docs":"Visit /docs for interactive documentation and endpoint details"}}
```

## Local build (same image)

```bash
docker compose build
docker compose up
```
Loading
Loading