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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
dist
.env
prisma/generated
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/DATABASE"
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: Deploy to AWS EC2

on:
push:
branches:
- main
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- latest

jobs:
deploy:
runs-on: ubuntu-latest

env:
AWS_REGION: ${{ vars.AWS_REGION }}
ECR_REPOSITORY: ${{ vars.ECR_REPOSITORY }}
CONTAINER_NAME: ${{ vars.CONTAINER_NAME }}
CONTAINER_PORT: ${{ vars.CONTAINER_PORT }}

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}

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

- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}
${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Deploy to EC2
uses: appleboy/ssh-action@8743aa11bfbda97acb45c151ae7a2e0b203f1914
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
envs: ECR_REGISTRY,ECR_REPOSITORY,AWS_REGION,CONTAINER_NAME,CONTAINER_PORT
script: |
set -eu

# Auth
aws ecr get-login-password --region "$AWS_REGION" | \
docker login --username AWS --password-stdin "$ECR_REGISTRY"

# Pull latest image
docker pull "$ECR_REGISTRY/$ECR_REPOSITORY:${{ github.sha }}"

# Stop and remove existing container if running
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker stop "$CONTAINER_NAME"
docker rm "$CONTAINER_NAME"
fi

# Run database migrations
docker run --rm \
-e DATABASE_URL="${{ secrets.DATABASE_URL }}" \
"$ECR_REGISTRY/$ECR_REPOSITORY:${{ github.sha }}" \
npx prisma migrate deploy

# Run new container
docker run -d \
--name "$CONTAINER_NAME" \
--restart unless-stopped \
-p "$CONTAINER_PORT:3000" \
Comment thread
DiluDevX marked this conversation as resolved.
-e DATABASE_URL="${{ secrets.DATABASE_URL }}" \
"$ECR_REGISTRY/$ECR_REPOSITORY:${{ github.sha }}"
Comment thread
DiluDevX marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# Verify container is running
sleep 5
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Container $CONTAINER_NAME is running"
docker ps --filter "name=$CONTAINER_NAME" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
else
echo "Container $CONTAINER_NAME failed to start"
docker logs "$CONTAINER_NAME" --tail 50
exit 1
fi

# Prune old images
docker image prune -f
4 changes: 4 additions & 0 deletions deployment-platforms/rest-express-docker-aws-ec2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
dist
.env
prisma/generated
35 changes: 35 additions & 0 deletions deployment-platforms/rest-express-docker-aws-ec2/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Stage 1: builder
FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./
COPY prisma ./prisma
COPY prisma.config.ts ./
RUN npm install
RUN npx prisma generate
Comment thread
DiluDevX marked this conversation as resolved.

COPY tsconfig.json ./
COPY src ./src
RUN npm run build

# Stage 2: runner
FROM node:20-alpine AS runner

WORKDIR /app

RUN addgroup -S appgroup && adduser -S appuser -G appgroup

COPY package*.json ./
COPY prisma ./prisma
COPY prisma.config.ts ./
RUN npm install --omit=dev

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/prisma/generated ./prisma/generated

USER appuser

EXPOSE 3000

CMD ["node", "dist/src/index.js"]
181 changes: 181 additions & 0 deletions deployment-platforms/rest-express-docker-aws-ec2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# REST API with Express, Docker & AWS EC2

This example shows how to deploy a **Prisma REST API** (Express + TypeScript) to **AWS EC2** using **Docker** and **GitHub Actions**.

## Prerequisites

- [Docker](https://www.docker.com/) and Docker Compose (for the Docker path)
- [Node.js](https://nodejs.org/) 20+ and a local PostgreSQL instance (for the non-Docker path)
- An AWS account (for deployment only)

## Getting started

### 1. Clone the repository

```sh
git clone https://github.com/prisma/prisma-examples.git --depth=1
cd prisma-examples/deployment-platforms/rest-express-docker-aws-ec2
```

### 2. Run the app

Choose one of the two local development paths below.

---

#### Option A — Docker Compose (recommended)

Copy the example env file (Docker Compose sets `DATABASE_URL` automatically, so no edits needed):

```sh
cp .env.example .env
```

Start the app and a local Postgres database:

```sh
docker compose up --build
```

The server is now running at `http://localhost:3000`. Migrations are applied automatically on startup.

Comment thread
DiluDevX marked this conversation as resolved.
---

#### Option B — Local Node.js + PostgreSQL

Create a `.env` file and set `DATABASE_URL` to your local database:

```sh
cp .env.example .env
# edit .env and set DATABASE_URL, e.g.:
# DATABASE_URL="postgresql://prisma:prisma@localhost:5432/prisma"
```

Install dependencies and generate the Prisma Client:

```sh
npm install
npx prisma migrate dev
```

Start the development server:

```sh
npm run dev
```

The server is now running at `http://localhost:3000`.

---

### 3. Use the REST API

Create a user:

```sh
curl -X POST http://localhost:3000/user \
-H "Content-Type: application/json" \
-d '{"email": "alice@prisma.io", "name": "Alice"}'
```

Create a post:

```sh
curl -X POST http://localhost:3000/post \
-H "Content-Type: application/json" \
-d '{"title": "Hello Prisma", "content": "My first post", "authorEmail": "alice@prisma.io"}'
```

Publish a post:

```sh
curl -X PUT http://localhost:3000/publish/1
```

Fetch all published posts:

```sh
curl http://localhost:3000/feed
```

Fetch a single post:

```sh
curl http://localhost:3000/post/1
```

Delete a post:

```sh
curl -X DELETE http://localhost:3000/post/1
```

---

## Deploying to AWS EC2

### 1. Set up AWS prerequisites

**ECR repository** — create one if you haven't already:

```sh
aws ecr create-repository --repository-name my-prisma-app --region us-east-1
```

**EC2 instance** — launch an instance (Amazon Linux 2 or Ubuntu) and install Docker:

```sh
# Amazon Linux 2
sudo yum update -y
sudo amazon-linux-extras install docker -y
sudo service docker start
sudo usermod -aG docker ec2-user
```

The EC2 instance needs permission to pull images from ECR. Choose one option:

- **Option 1 (recommended):** Attach an IAM instance role with the `AmazonEC2ContainerRegistryReadOnly` policy. No credentials are stored on the instance.
- **Option 2:** Run `aws configure` on the instance and enter an IAM access key that has ECR read permissions.

This is required by the `deploy.yml` step that runs `aws ecr get-login-password` on the instance before pulling the Docker image.

Make sure port `3000` (or your chosen `CONTAINER_PORT`) is open in the instance's security group.

### 2. Configure GitHub secrets and variables

In your repository, go to **Settings → Secrets and variables → Actions** and add:

**Secrets** (sensitive values):

| Name | Description |
|---|---|
| `AWS_ACCESS_KEY_ID` | AWS IAM access key with ECR and EC2 permissions |
| `AWS_SECRET_ACCESS_KEY` | Corresponding secret key |
| `EC2_HOST` | Public IP or DNS of your EC2 instance |
| `EC2_USER` | SSH username (e.g. `ec2-user` or `ubuntu`) |
| `EC2_SSH_KEY` | Private SSH key used to connect to EC2 |
| `DATABASE_URL` | PostgreSQL connection string for your production database |

**Variables** (non-sensitive values):

| Name | Example value |
|---|---|
| `AWS_REGION` | `us-east-1` |
| `ECR_REPOSITORY` | `my-prisma-app` |
| `CONTAINER_NAME` | `prisma-app` |
| `CONTAINER_PORT` | `3000` |

### 3. How deployment works

Copy [`.github/workflows/deploy.yml`](./.github/workflows/deploy.yml) to `.github/workflows/` at the root of **your own repository**. Pushing to `main` or `latest` triggers the workflow, which performs the following steps:

1. Authenticates with AWS using the configured IAM credentials.
2. Builds a Docker image using Buildx with GitHub Actions layer caching for faster rebuilds.
3. Pushes the image to ECR tagged with both the commit SHA and `latest`.
4. SSHs into your EC2 instance.
5. Runs `prisma migrate deploy` against your production database in a one-off container.
6. Pulls the new image.
7. Stops and removes the old container if one is running.
8. Starts the new container with `DATABASE_URL` injected at runtime.
9. Waits 5 seconds and verifies the container is running — prints logs and exits non-zero on failure.
10. Prunes old images to keep the EC2 disk clean.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
services:
app:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://prisma:prisma@postgres:5432/prisma
depends_on:
postgres:
condition: service_healthy
command: >
sh -c "npx prisma migrate deploy && node dist/src/index.js"

postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: prisma
POSTGRES_PASSWORD: prisma
POSTGRES_DB: prisma
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U prisma"]
interval: 5s
timeout: 5s
retries: 5
26 changes: 26 additions & 0 deletions deployment-platforms/rest-express-docker-aws-ec2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "rest-express-docker-aws-ec2",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"build": "prisma generate && tsc",
"typecheck": "tsc --noEmit",
"dev": "prisma generate && npm run typecheck && tsx src/index.ts",
"start": "node dist/index.js"
},
"dependencies": {
"@prisma/adapter-pg": "7.5.0",
"@prisma/client": "7.5.0",
"dotenv": "^17.2.1",
"express": "5.1.0",
"pg": "^8.16.3",
"prisma": "7.5.0"
},
"devDependencies": {
"@types/express": "5.0.5",
"@types/node": "22.18.12",
"@types/pg": "^8.15.6",
"tsx": "^4.20.6",
"typescript": "5.8.2"
}
}
12 changes: 12 additions & 0 deletions deployment-platforms/rest-express-docker-aws-ec2/prisma.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineConfig } from 'prisma/config'
import 'dotenv/config'

export default defineConfig({
schema: 'prisma/schema.prisma',
migrations: {
path: 'prisma/migrations',
},
datasource: {
url: process.env.DATABASE_URL ?? '',
},
})
Loading
Loading