Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save AlexandroMtzG/4d6ad44e9e0dbef8c6de12e3558a730a to your computer and use it in GitHub Desktop.
Save AlexandroMtzG/4d6ad44e9e0dbef8c6de12e3558a730a to your computer and use it in GitHub Desktop.

Deploy Remix to AWS Lightsail with Docker

Read the full tutorial here.

Deploy Remix to AWS Lightsail with Docker

Requirements:

Find and replace:

  • saasrock-demo-service with your AWS Lightsail service name
  • saasrock-demo-image with your image name
  • us-east-1 with your preferred region

Deployment Steps:

💿 1. Build the image

docker build -t saasrock-demo-image .

💿 2. Run the image locally

docker run -p 8080:8080 --env-file .env.demo saasrock-demo-image:latest

💿 3. Create a Lightsail container service

aws lightsail create-container-service --region us-east-1 --service-name saasrock-demo-service --power nano --scale 1

This is creating a Nano instance ($7/month) with 1 instance:

AWS Lightsail instance sizes

You can check the status of the service with:

aws lightsail get-container-services --region us-east-1 --service-name saasrock-demo-service --query "containerServices[].state"

💿 4. Push the image to the AWS Lightsail container service

aws lightsail push-container-image --region us-east-1 --service-name saasrock-demo-service --label latest --image saasrock-demo-image:latest

💿 5. Create an AWS configuration file aws-lightsail-containers.json

{
  "serviceName": "saasrock-demo-service",
  "containers": {
    "saasrock-demo-service": {
      "image": "saasrock-demo-image:latest",
      "environment": {
        "APP_NAME": "SaasRock Dev AWS Lightsail"
        // ...
      },
      "ports": {
        "8080": "HTTP"
      }
    }
  },
  "publicEndpoint": {
    "containerName": "saasrock-demo-service",
    "containerPort": 8080
  }
}

⚠️ IMPORTANT: Add this file to your .gitignore to avoid sharing sensitive information.

💿 6. Deploy the image to the AWS Lightsail container service

aws lightsail create-container-service-deployment --region us-east-1 --cli-input-json file://aws-lightsail-containers.json

Check the status of the deployment with:

aws lightsail get-container-services --region us-east-1  --query "containerServices[].nextDeployment.state"

Once it's deployed, grab the public endpoint with:

aws lightsail get-container-services --region us-east-1 --service-name saasrock-demo-service --query "containerServices[].url"

And that's it! You can access your app at the public endpoint provided by AWS Lightsail.

AWS Lightsail Deployment

And if you check your Amazon Lightsail dashboard, you should see your container service:

AWS Lightsail Container Service

Deploying Updates:

💿 1. Build the image as in step 1

docker build -t saasrock-demo-image .

💿 2. Push the image as in step 4

aws lightsail push-container-image --region us-east-1 --service-name saasrock-demo-service --label latest --image saasrock-demo-image:latest

💿 3. Grab the new image tag

Digest: sha256:5723533c861348b69369fb79d85c6b203713c33b49f0b1b813d9d9bf71454732
Image "saasrock-demo-image:latest" registered.
Refer to this image as ":saasrock-demo-service.latest.4" in deployments.

In this case, the image tag is saasrock-demo-service.latest.4.

💿 4. Change the image tag in the aws-lightsail-containers.json file

{
  ...
  "containers": {
    "saasrock-demo-service": {
      "image": "UPDATE_IMAGE_TAG_HERE",
      ...
}

💿 5. Deploy the image as in step 6

aws lightsail create-container-service-deployment --region us-east-1 --cli-input-json file://aws-lightsail-containers.json

Wait for the deployment to finish, you can check the status with:

aws lightsail get-container-services --region us-east-1 --service-name saasrock-demo-service --query "containerServices[].state"

And that's it! Your app is now updated.

ℹ️ NOTE: If you forgot the URL of your app, you can get it with:

aws lightsail get-container-services --region us-east-1 --service-name saasrock-demo-service --query "containerServices[].url"

Required files

Dockerfile:

#!/bin/bash

# base node image
FROM --platform=linux/amd64 node:18-bookworm-slim as base

# set for base and all layer that inherit from it
ENV NODE_ENV production

# Install openssl for Prisma
RUN apt-get update && apt-get install -y openssl

# Install all node_modules, including dev dependencies
FROM base as deps

WORKDIR /myapp

ADD package.json ./
RUN npm install --production=false --legacy-peer-deps

# Setup production node_modules
FROM base as production-deps

WORKDIR /myapp

COPY --from=deps /myapp/node_modules /myapp/node_modules
ADD package.json ./
RUN npm prune --production --legacy-peer-deps

# Build the app
FROM base as build

WORKDIR /myapp

COPY --from=deps /myapp/node_modules /myapp/node_modules

ADD prisma .
RUN npx prisma generate

ADD . .
RUN npm run build

# Finally, build the production image with minimal footprint
FROM base

ENV PORT="8080"
ENV NODE_ENV="production"

WORKDIR /myapp

COPY --from=production-deps /myapp/node_modules /myapp/node_modules
COPY --from=build /myapp/node_modules/.prisma /myapp/node_modules/.prisma

COPY --from=build /myapp/build /myapp/build
COPY --from=build /myapp/public /myapp/public
COPY --from=build /myapp/package.json /myapp/package.json
COPY --from=build /myapp/start.sh /myapp/start.sh
COPY --from=build /myapp/prisma /myapp/prisma

RUN chmod +x /myapp/start.sh

ENTRYPOINT [ "./start.sh" ]

start.sh:

#!/bin/sh
npm run start

.dockerignore

/node_modules
*.log
.DS_Store
.env
/.cache
/public/build
/build

package.json:

{
  ...
  "scripts": {
    ...
    "build": "remix vite:build",
    "start": "remix-serve ./build/index.js"
  },
  "dependencies": {
    ...
  },
  "devDependencies": {
    ...
  }
}

vite.config.ts:

import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
import remixConfig from "./remix.config";
import tsconfigPaths from "vite-tsconfig-paths";
import { vercelPreset } from "@vercel/remix/vite";

export default defineConfig({
  server: { port: 3000 },
  plugins: [
    tsconfigPaths(),
    remix({
      ...remixConfig,
      presets: [vercelPreset],
    }),
  ],
  ssr: {
    noExternal: ["remix-i18next"],
  },
  resolve: {
    alias: {
      ".prisma/client/index-browser": "./node_modules/.prisma/client/index-browser.js",
    },
  },
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment