feat: implement automated Docker deployment and update README

- Replaced manual rsync/npm_deploy workflow with multi-stage Docker builds.
- Added Dockerfile and .dockerignore for staging and production environments.
- Added 'deploy:staging' and 'deploy:prod' scripts to package.json.
- Updated README.md with new deployment instructions.
This commit is contained in:
Scott Idem
2026-03-09 22:17:54 -04:00
parent 206faf0c71
commit 9b285d7fec
5 changed files with 89 additions and 23 deletions

12
.dockerignore Normal file
View File

@@ -0,0 +1,12 @@
node_modules
build
.svelte-kit
.git
.env
.env.*
!.env.staging
!.env.prod
npm_deploy
test-results
test_results
documentation

46
Dockerfile Normal file
View File

@@ -0,0 +1,46 @@
# Stage 1: Build the application
# Using node:21-alpine for a lightweight, secure build environment.
FROM node:21-alpine AS builder
WORKDIR /app
# Install dependencies first for better Docker layer caching.
# This step only reruns if package.json or package-lock.json changes.
COPY package*.json ./
RUN npm install
# Copy the rest of the source code.
COPY . .
# Build Argument to determine build environment (staging, prod, or production).
# Defaults to "staging".
ARG BUILD_MODE=staging
ENV NODE_ENV=production
# Perform the build based on the BUILD_MODE argument.
# This runs the existing scripts in package.json, which already
# handle copying the correct .env file to .env.production for Vite.
RUN if [ "$BUILD_MODE" = "prod" ] || [ "$BUILD_MODE" = "production" ]; then \
npm run build:prod; \
else \
npm run build:staging; \
fi
# Stage 2: Final runtime image
FROM node:21-alpine AS deploy-node
WORKDIR /app
# Copy only the built files and necessary scripts from the builder stage.
COPY --from=builder /app/build .
COPY --from=builder /app/package.json .
# Install only production dependencies for a smaller, cleaner image.
RUN npm install --omit=dev
# Copy the resulting .env.production file to .env.
# adapter-node reads from .env at runtime for non-PUBLIC_ variables.
COPY --from=builder /app/.env.production .env
# SvelteKit (via adapter-node) defaults to port 3000.
EXPOSE 3000
CMD ["node", "index.js"]

View File

@@ -123,36 +123,34 @@ Developer sandbox pages — not for production use.
# How to build and deploy SvelteKit:
Copy the contents of the "build" directory to ./npm_deploy/build/
The deployment is now fully automated using Docker Compose. The application is built directly from source inside a clean Docker environment, eliminating the need for manual `rsync` or `npm_deploy` management.
### Commands
Run these commands from the root of the `aether_app_sveltekit` project:
#### 1. Deploy to Staging (Dev)
This builds and restarts the staging containers (`ae_app_node_dev`, etc.) on your local workstation.
```bash
npm run build
npm run deploy:staging
```
If this is just a quick build update then only the build directory needs to be copied (rsync).
#### 2. Deploy to Production
This builds the image using production flags and restarts the production containers.
```bash
rsync -vhrz --exclude 'node_modules' ~/OSIT_dev/aether_app_sveltekit/build/ ~/OSIT_dev/ae_env_node_app/npm_deploy/build/ --delete
rsync -vhrz ~/OSIT_dev/ae_env_node_app/npm_deploy/build/ scott@linode.oneskyit.com:/srv/env/prod_aether_sveltekit/npm_deploy/build/ --delete
npm run deploy:prod
```
If this includes package updates (not development) we need to copy the new package.json. Manually copy the new package.json file to ./npm_deploy/. This also needs to be copied to the server. Copy the package.json even though not really used.
### Technical Details
Run the --omit dev to clear out the node_modules directory. Copy the root node_modules directory to ./npm_deploy/build/node_modules/ after running te omit dev command.
- **Dockerfile**: Uses a multi-stage build. Stage 1 (builder) installs dependencies and builds the app using the `BUILD_MODE` argument. Stage 2 (runtime) creates the final lightweight image.
- **Environment Handling**:
- `PUBLIC_` variables are baked into the image during the build step based on `.env.staging` or `.env.prod`.
- Private runtime variables are passed via the `env_file` in `docker-compose.yml`.
- **Networking**: Containers are automatically joined to the `ae_dev_net` network to allow local Nginx proxying.
- **Legacy Migration**: The `upstream` in the local Nginx configuration has been updated to point to these new SvelteKit containers on port 3000.
```bash
npm ci --omit dev
# copy/paste, rsync, or cp
rsync -vhrz ~/OSIT_dev/aether_app_sveltekit/node_modules ~/OSIT_dev/ae_env_node_app/npm_deploy/build/ --delete
# copy package.json as well
npm install
```
Everything should be ready to run on the development server and production server.
---
# Rebuild the node_modules directory and manually install extra Svelte packages

View File

@@ -16,7 +16,9 @@
"lint": "prettier --check . && eslint .",
"format": "prettier --write .",
"test:integration": "playwright test",
"test:unit": "vitest"
"test:unit": "vitest",
"deploy:staging": "docker compose -f ../ae_env_node_app/docker-compose.yml up -d --build --remove-orphans",
"deploy:prod": "docker compose -f ../ae_env_node_app/docker-compose.yml build --build-arg BUILD_MODE=prod && docker compose -f ../ae_env_node_app/docker-compose.yml up -d --remove-orphans"
},
"devDependencies": {
"@eslint/js": "^9.39.1",

View File

@@ -20,6 +20,7 @@
// *** Libraries & Stores
import { liveQuery } from 'dexie';
import { Modal } from 'flowbite-svelte';
import { db_core } from '$lib/ae_core/db_core';
import { db_journals } from '$lib/ae_journals/db_journals';
import { journals_func } from '$lib/ae_journals/ae_journals_functions';
import { ae_loc, ae_api, slct } from '$lib/stores/ae_stores';
@@ -47,6 +48,13 @@
let log_lvl = 0;
// *** LiveQueries
let lq__account = $derived(
liveQuery(async () => {
if (!$slct.account_id) return null;
return await db_core.account.get($slct.account_id);
})
);
let lq__journal_obj_li = $derived(
liveQuery(async () => {
return await db_journals.journal
@@ -105,7 +113,7 @@
</h1>
<p class="text-surface-600 dark:text-surface-400 font-medium">
Managed by <span class="text-primary-500"
>{$ae_loc.account_name ?? 'Æ loading...'}</span
>{$lq__account?.name ?? $ae_loc.account_name ?? 'Æ loading...'}</span
>
{#if $ae_loc.person.given_name}
&bull; <span class="opacity-75"