-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat: add rest-express-docker-aws-ec2 deployment example #8473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
AmanVarshney01
merged 8 commits into
prisma:latest
from
DiluDevX:add-rest-express-docker-aws-ecs
Apr 7, 2026
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
66b7efc
feat: add rest-express-docker-aws-ec2 deployment example
DiluDevX 3d946bf
fix: address Copilot review feedback
DiluDevX 9d4d99d
address PR review: input validation, error handling, env guard, dynam…
DiluDevX 03028dc
address PR review: author lookup, 201 on create, authorId index, type…
DiluDevX d88176f
address PR review: try/catch on all handlers, 201 for POST /user
DiluDevX 673be65
fix: resolve runtime module resolution and build configuration issues
DiluDevX 8b315c1
fix(rest-express-docker-aws-ec2): fix Dockerfile CMD path, bump Prism…
2b45a4e
revert: restore correct Dockerfile CMD path
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
4 changes: 4 additions & 0 deletions
4
deployment-platforms/rest-express-docker-aws-ec2/.dockerignore
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| node_modules | ||
| dist | ||
| .env | ||
| prisma/generated |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/DATABASE" |
99 changes: 99 additions & 0 deletions
99
deployment-platforms/rest-express-docker-aws-ec2/.github/workflows/deploy.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| name: Deploy to AWS EC2 | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
| - 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" \ | ||
|
DiluDevX marked this conversation as resolved.
|
||
| -e DATABASE_URL="${{ secrets.DATABASE_URL }}" \ | ||
| "$ECR_REGISTRY/$ECR_REPOSITORY:${{ github.sha }}" | ||
|
DiluDevX marked this conversation as resolved.
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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
35
deployment-platforms/rest-express-docker-aws-ec2/Dockerfile
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
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
181
deployment-platforms/rest-express-docker-aws-ec2/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. | ||
|
|
||
|
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. | ||
26 changes: 26 additions & 0 deletions
26
deployment-platforms/rest-express-docker-aws-ec2/docker-compose.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
26
deployment-platforms/rest-express-docker-aws-ec2/package.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
12
deployment-platforms/rest-express-docker-aws-ec2/prisma.config.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 ?? '', | ||
| }, | ||
| }) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.