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
84 changes: 84 additions & 0 deletions .github/workflows/deploy-lightsail.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Deploy Lightsail

on:
push:
branches:
- main
workflow_dispatch:

concurrency:
group: lightsail-production
cancel-in-progress: false

jobs:
verify:
name: Verify
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Test API
run: npm run test --workspace @nail-star/api -- --runInBand

- name: Test web
run: npm run test:web

- name: Build API
run: npm run build:api

- name: Build web
run: npm run build:web

- name: Build API Docker image
run: npm run docker:api

deploy:
name: Deploy
runs-on: ubuntu-latest
needs: verify
environment: production

steps:
- name: Deploy over SSH
uses: appleboy/ssh-action@v1.2.0
env:
APP_DIR: ${{ secrets.LIGHTSAIL_APP_DIR }}
BACKEND_URL: ${{ secrets.PRODUCTION_BACKEND_URL }}
with:
host: ${{ secrets.LIGHTSAIL_HOST }}
username: ${{ secrets.LIGHTSAIL_USER }}
key: ${{ secrets.LIGHTSAIL_SSH_KEY }}
envs: APP_DIR,BACKEND_URL
script_stop: true
script: |
set -eu
cd "$APP_DIR"

git fetch origin main
git reset --hard origin/main

npm ci
VITE_BACKEND_URL="$BACKEND_URL" npm run build:web

sudo mkdir -p /srv/nail-star/web
sudo rsync -a --delete apps/web/dist/ /srv/nail-star/web/

docker compose -f deploy/lightsail/compose.yml --env-file deploy/lightsail/.env up -d --build

HEALTH_BASE="${BACKEND_URL%/api}"
curl -fsS "$HEALTH_BASE/healthz"
curl -fsS "$HEALTH_BASE/api/healthz"
14 changes: 9 additions & 5 deletions deploy/lightsail/Caddyfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
{$APP_DOMAIN} {
encode zstd gzip

handle /api/* {
@api path /api/*
handle @api {
uri strip_prefix /api
reverse_proxy api:8080
}

handle /healthz {
@health path /healthz
handle @health {
reverse_proxy api:8080
}

root * /srv/nail-star/web
try_files {path} /index.html
file_server
handle {
root * /srv/nail-star/web
try_files {path} /index.html
file_server
}
}
3 changes: 2 additions & 1 deletion deploy/lightsail/api.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ DB_HOST=your-lightsail-postgres-endpoint
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=replace-me
DB_NAME=nail_star
DB_NAME=postgres
DB_SSL=true
JWT_SECRET=replace-me-with-a-long-random-secret
CORS_ORIGINS=https://example.com
COOKIE_SECURE=true
Expand Down
20 changes: 18 additions & 2 deletions docs/deploy-lightsail.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This deployment keeps monthly cost low while separating the database from the ap
- Lightsail managed PostgreSQL for application data.
- Optional S3 or Lightsail object storage for off-instance database dumps.

Expected baseline cost is roughly $30-$35/month before optional snapshot and backup storage growth.
Expected baseline cost is roughly $30-$45/month before optional snapshot and backup storage growth, depending on the managed database plan.

## DNS

Expand All @@ -23,7 +23,7 @@ cp deploy/lightsail/.env.example deploy/lightsail/.env
cp deploy/lightsail/api.env.example deploy/lightsail/api.env
```

Update both files with the production domain, database endpoint, credentials, JWT secret, and SMTP credentials.
Update both files with the production domain, database endpoint, credentials, JWT secret, and SMTP credentials. Lightsail PostgreSQL requires SSL in this setup, so keep `DB_SSL=true` in `deploy/lightsail/api.env`.

Build and publish the frontend files:

Expand All @@ -40,6 +40,8 @@ Start the API and reverse proxy:
docker compose -f deploy/lightsail/compose.yml --env-file deploy/lightsail/.env up -d --build
```

If deploying before buying a domain, set `APP_DOMAIN=:80`, build the frontend with `VITE_BACKEND_URL=http://<static-ip>/api`, set `CORS_ORIGINS=http://<static-ip>`, and set `COOKIE_SECURE=false`. Switch those values to HTTPS and `COOKIE_SECURE=true` after the domain points to the instance.

Verify:

```bash
Expand Down Expand Up @@ -84,3 +86,17 @@ PGPASSWORD="$DB_PASSWORD" pg_dump -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -Fc
```

Store dumps outside the app server, such as S3, Lightsail object storage, or another backup target.

## GitHub Actions Deployment

The workflow in `.github/workflows/deploy-lightsail.yml` deploys `main` by SSHing into the Lightsail instance and running the same commands as a manual deploy.

Create these repository secrets:

- `LIGHTSAIL_HOST`: the instance static IP or domain.
- `LIGHTSAIL_USER`: usually `ubuntu`.
- `LIGHTSAIL_SSH_KEY`: the private SSH key that can connect to the instance.
- `LIGHTSAIL_APP_DIR`: usually `/opt/nail-star`.
- `PRODUCTION_BACKEND_URL`: for example `http://52.201.200.185/api` now, later `https://yourdomain.com/api`.

The server still needs its local `deploy/lightsail/.env` and `deploy/lightsail/api.env` files because they contain runtime secrets that should not be committed.