509 lines
24 KiB
Markdown
509 lines
24 KiB
Markdown
# Container, staging, and production deployment
|
|
|
|
SHD.build runs in a Node 24 container through the official SvelteKit Node
|
|
adapter. The image includes application code, migration and notification
|
|
scripts, the Infisical CLI, and native runtime dependencies. Databases, backups,
|
|
user uploads, and Universal Auth bootstrap credentials remain outside the image.
|
|
|
|
## Persistent data
|
|
|
|
The staging compose project uses the named volume `shd-build-stage-data`,
|
|
mounted at `/app/data`. It contains:
|
|
|
|
- `shd-build-staging.db` and its embedded-database support files;
|
|
- timestamped JSON backups under `backups/`;
|
|
- normalized user avatars under `avatars/`;
|
|
- uploaded game-data icons under `game-icons/`.
|
|
|
|
Keep this volume during routine deployments. `docker compose down` without
|
|
`--volumes` preserves it. Before a manual backup or restore, stop the `app` and
|
|
`scheduler` services because both access the embedded database.
|
|
|
|
## Staging environment
|
|
|
|
Staging defaults to:
|
|
|
|
- Public origin: `https://stage.shd.build`
|
|
- Reverse-proxy upstream: host port `5569`
|
|
- Database: `/app/data/shd-build-staging.db`
|
|
- Passkey relying-party ID: `shd.build`
|
|
- Adapter canonical origin: `https://stage.shd.build`
|
|
|
|
Guide ratings and reports use Cloudflare Turnstile after repeated anonymous
|
|
activity. Configure both values under `/app` in each Infisical environment:
|
|
|
|
```text
|
|
TURNSTILE_SITE_KEY=<public site key>
|
|
TURNSTILE_SECRET_KEY=<server verification secret>
|
|
```
|
|
|
|
Register the exact staging and production hostnames in the Turnstile widget. The
|
|
browser receives only `TURNSTILE_SITE_KEY`. The server sends a challenge token
|
|
and the request address to Cloudflare for verification; it never exposes
|
|
`TURNSTILE_SECRET_KEY` or stores the raw address as visitor identity. Without a
|
|
valid secret, a visitor who reaches the challenge threshold cannot submit more
|
|
anonymous ratings or Guide reports.
|
|
|
|
Edit staging configuration in Infisical project
|
|
`2ee94b27-e599-4194-9797-3372d42e53f6`:
|
|
|
|
```text
|
|
Environment: staging
|
|
Application path: /app
|
|
Scheduler path: /shared/scheduler
|
|
```
|
|
|
|
Add `https://stage.shd.build/auth/discord/callback` to the Discord application's
|
|
allowed OAuth redirect URIs. Point the staging DNS record at the deployment host
|
|
and configure the host's TLS reverse proxy to forward `stage.shd.build` to port
|
|
`5569` on the staging host. Docker publishes this port on the host interfaces so
|
|
a containerized reverse proxy or tunnel can reach it; firewall the port to
|
|
trusted LAN/tunnel sources when the host is reachable from other networks.
|
|
|
|
The trusted staging runner writes separate mode-`0600` Universal Auth client-ID
|
|
and client-secret files for the app and scheduler. Compose mounts each pair only
|
|
into its assigned service. Each container exchanges those credentials for a
|
|
short-lived access token and writes the assigned paths to mode-`0600` temporary
|
|
JSON files. The entrypoint rejects unknown secret names, deletes the token,
|
|
transfers the exports to the dedicated `shd-runtime` UID 10001, and drops root
|
|
before parsing them. The launcher deletes the files before validation,
|
|
migration, or startup. Startup fails if the mounted credential files are
|
|
readable by UID 10001 or by their group or other users. Runtime-created data
|
|
retains group access for rollback to the previous container UID.
|
|
|
|
| Machine identity | Environment | Injected paths | Gitea variable | Gitea secret |
|
|
| -------------------------------- | ----------- | --------------------------- | ------------------------------------------ | ---------------------------------------------- |
|
|
| `shd-build-staging-app` | `staging` | `/app`, `/shared/scheduler` | `INFISICAL_STAGING_APP_CLIENT_ID` | `INFISICAL_STAGING_APP_CLIENT_SECRET` |
|
|
| `shd-build-staging-scheduler` | `staging` | `/shared/scheduler` | `INFISICAL_STAGING_SCHEDULER_CLIENT_ID` | `INFISICAL_STAGING_SCHEDULER_CLIENT_SECRET` |
|
|
| `shd-build-production-app` | `prod` | `/app`, `/shared/scheduler` | `INFISICAL_PRODUCTION_APP_CLIENT_ID` | `INFISICAL_PRODUCTION_APP_CLIENT_SECRET` |
|
|
| `shd-build-production-scheduler` | `prod` | `/shared/scheduler` | `INFISICAL_PRODUCTION_SCHEDULER_CLIENT_ID` | `INFISICAL_PRODUCTION_SCHEDULER_CLIENT_SECRET` |
|
|
|
|
The self-hosted Free plan allows five machine identities and does not include
|
|
Custom Project Roles. The four runtime identities use the built-in Viewer role,
|
|
which can read the complete project. Service-specific paths limit injected
|
|
values, but they do not restrict what a stolen root credential could request.
|
|
Use an Enterprise license with Custom Project Roles, or split runtime scopes
|
|
into separate projects, before treating identity authorization as least
|
|
privilege.
|
|
|
|
An Infisical outage does not stop a running container. A new container or direct
|
|
restart fails closed until `https://secrets.jb.codes` becomes available and the
|
|
identity can authenticate.
|
|
|
|
Keep the old `.env.*.runtime` files and `DOTENV_PRIVATE_KEY_*` Gitea secrets
|
|
only through the first successful staging and production cutovers. The rollback
|
|
path may need them to start the previous image. After both environments pass
|
|
direct restart checks, rotate every migrated provider credential in Infisical,
|
|
remove the old dotenv private keys and `SCHEDULER_TOKEN_PRODUCTION` from Gitea,
|
|
and delete local and hosted dotenv key material. Removing encrypted files from
|
|
the current Git tree does not revoke values stored in Git history.
|
|
|
|
The staging container disables default-user seeding. On a brand-new staging
|
|
database, the existing bootstrap policy grants Admin to the first real account
|
|
that signs in instead of writing generated credentials into deployment logs.
|
|
|
|
## Gitea Actions runner
|
|
|
|
Gitea Actions requires an `act_runner`. Register a trusted runner on the staging
|
|
host from the repository's **Settings → Actions → Runners** page. The host needs
|
|
Node/npm, Docker Engine, and Docker Compose v2. The runner account must be able
|
|
to use Docker and write to the deployment directory.
|
|
|
|
Use runner labels that provide an isolated validation job and a host deployment
|
|
job:
|
|
|
|
```text
|
|
ubuntu-latest:docker://node:24-bookworm,stage-deploy:host
|
|
```
|
|
|
|
The `stage-deploy:host` label is intentional: the deployment step needs the
|
|
staging host's Docker daemon and persistent named volume. Only attach this
|
|
trusted runner to repositories allowed to deploy to that host. Gitea warns that
|
|
Docker daemon access is effectively host-level authority.
|
|
|
|
Prepare the default deployment directory:
|
|
|
|
```sh
|
|
sudo install -d -o act_runner -g act_runner -m 0750 /opt/shd-build-stage
|
|
sudo usermod -aG docker act_runner
|
|
```
|
|
|
|
Restart the runner session after adding it to the Docker group.
|
|
|
|
## Repository Actions configuration
|
|
|
|
In `https://git.jb.codes/jb/shd.build/settings/actions`:
|
|
|
|
1. Add repository secrets `INFISICAL_STAGING_APP_CLIENT_SECRET` and
|
|
`INFISICAL_STAGING_SCHEDULER_CLIENT_SECRET`.
|
|
2. Add repository variables `INFISICAL_STAGING_APP_CLIENT_ID` and
|
|
`INFISICAL_STAGING_SCHEDULER_CLIENT_ID`.
|
|
3. Optionally add repository variable `STAGE_DEPLOY_PATH`. It defaults to
|
|
`/opt/shd-build-stage`.
|
|
4. Optionally add repository variable `STAGE_PORT`. It defaults to `5569`.
|
|
5. Ensure Actions are enabled and the `stage-deploy` runner is online.
|
|
|
|
`.gitea/workflows/deploy-staging.yml` runs for pushes to `main` and manual
|
|
dispatches. The trusted staging runner builds each image locally with a tag that
|
|
contains the commit, workflow run, and attempt, so the workflow needs no
|
|
registry credentials and a retry cannot overwrite the active image. Gitea
|
|
assigns `gitea.run_number` to `SHD_BUILD_NUMBER`; each new workflow run receives
|
|
the next integer, while retries of the same run retain their number.
|
|
|
|
Staging and production deployments, ISAC exports, and production-to-staging
|
|
synchronization share the `shd-build-host-maintenance` concurrency group. Gitea
|
|
runs only one of these database maintenance workflows on the deployment host at
|
|
a time and never cancels one in progress.
|
|
|
|
## Deployment lifecycle
|
|
|
|
The workflow stages Compose, credentials, and release metadata in an isolated
|
|
candidate directory. It leaves the canonical deployment files unchanged until
|
|
the candidate passes preflight, backup, and migration. The workflow then:
|
|
|
|
1. installs dependencies, runs `npm run check` and `npm test`, and produces the
|
|
Node build;
|
|
2. builds the candidate container while the current site remains online;
|
|
3. starts one-off app and scheduler containers to verify Infisical access and
|
|
validate the candidate environment;
|
|
4. stops only the staging application and notification scheduler;
|
|
5. creates a timestamped JSON backup if the staging database exists;
|
|
6. applies additive, repeatable migrations in a one-off container;
|
|
7. promotes the candidate files to the canonical deployment paths;
|
|
8. starts the application and scheduler with the new image;
|
|
9. validates the app environment and reruns the repeatable migration in the app
|
|
entrypoint before serving;
|
|
10. verifies `/api/health` and scheduler liveness;
|
|
11. restores the previous canonical files and verifies the previous app and
|
|
scheduler after a failed stop, backup, migration, startup, or health check.
|
|
|
|
On a failed health check, the deploy script prints the final 150 application log
|
|
lines and exits unsuccessfully. It does not create a separate log artifact.
|
|
|
|
The outage covers backup, migration, and container startup. Migrations may also
|
|
remove records that use retired data formats when a release explicitly ends
|
|
compatibility. The deployment backup remains the recovery source for those
|
|
records; the application does not repair or reinterpret them at runtime. The
|
|
scheduler calls a bearer-authenticated endpoint on the application service once
|
|
per minute. Scheduled message delivery therefore uses the web process's existing
|
|
embedded-database connection instead of opening the locked database from a
|
|
second process. The endpoint runs automated system messages, Oversight
|
|
consolidation, email digest checks, and the Ubisoft News importer. Docker does
|
|
not publish the endpoint through a host port.
|
|
|
|
When enabled in production, the Ubisoft importer keeps its own persisted due
|
|
time, so the one-minute tick starts a feed synchronization no more than once per
|
|
hour after a successful run. When a synchronization fails, the importer stores
|
|
the last error and consecutive failure count, then retries after 15 minutes with
|
|
exponential backoff capped at six hours. Source items that disappear from the
|
|
feed remain published; the importer performs no automatic removal. The scheduler
|
|
runs notification delivery before it starts the bounded Ubisoft request, so a
|
|
slow provider does not postpone notifications for the current tick.
|
|
|
|
The runtime launcher sets `UBISOFT_NEWS_IMPORT_ENABLED=true` for production and
|
|
`false` for staging. Development does not set the flag, so the importer remains
|
|
disabled because it requires the exact value `true`. The application process
|
|
evaluates the flag when it handles `/api/internal/scheduler`.
|
|
|
|
## Prelaunch operation
|
|
|
|
The launch-control migration creates `site_launch_settings` with these defaults
|
|
for a database that does not contain the row:
|
|
|
|
- Coming Soon: enabled
|
|
- Account creation: enabled
|
|
- News, Database, Builds, Guides, and Builder: enabled
|
|
|
|
The migration uses an insert-if-missing seed. Later deployments preserve the
|
|
operator's settings, and configuration changes take effect without a restart. A
|
|
full environment bootstrap copies the source row. Canonical ISAC game-data
|
|
imports leave the target environment's launch settings unchanged.
|
|
|
|
Use this operator flow after the first deployment:
|
|
|
|
1. Open `/sign-in`; the launch gate keeps that route available.
|
|
2. Sign in with an existing Administrator account.
|
|
3. Open ISAC → System Configuration → Site Controls.
|
|
4. Set account creation and public-section availability.
|
|
5. Review the complete change set, then confirm it.
|
|
6. Use `PREVIEW PAGE` to inspect `/coming-soon` with the authenticated
|
|
`ENTER FULL SITE` return action. The preview lists each section's current
|
|
availability.
|
|
7. Verify `/coming-soon` and the affected public routes in a signed-out browser
|
|
session before public release. Sign in and verify the same disabled routes
|
|
remain unavailable.
|
|
|
|
Keep Coming Soon enabled while preparing production. Section controls can be set
|
|
in advance because the launch gate takes precedence for signed-out traffic. Set
|
|
Account Creation to Disabled if prelaunch access must remain limited to existing
|
|
identities. Switch the Launch Gate to Public Launch after verification. Disabled
|
|
sections remain absent from navigation and unavailable by direct URL for every
|
|
user, including authenticated agents. Authentication bypasses only the Coming
|
|
Soon gate.
|
|
|
|
Turn Coming Soon on to restore the promotional gate. Site-control changes append
|
|
`system_configuration / site_access.updated` audit events, including the
|
|
operator and complete before/after settings.
|
|
|
|
## Initial development-data bootstrap
|
|
|
|
Initialize the first staging release from the development database, including
|
|
users, roles, builds, guides, proposals, audit history, avatars, and uploaded
|
|
game-data icons. The staging importer refuses a target database that contains
|
|
users, builds, audits, or proposals. It also requires the source and target
|
|
table sets to match exactly; snapshots with retired or unknown tables are
|
|
rejected.
|
|
|
|
First stop this project's development server. The embedded database must not be
|
|
open while its complete transfer bundle is created:
|
|
|
|
```sh
|
|
npm run stage:bootstrap:export
|
|
```
|
|
|
|
The command writes a timestamped, ignored JSON bundle under `data/backups/`. The
|
|
bundle includes user records and password hashes. Transfer it over an encrypted
|
|
channel, keep it out of Git, then copy it to the staging host and run:
|
|
|
|
```sh
|
|
/opt/shd-build-stage/bootstrap-staging.sh \
|
|
/secure/path/shd-stage-bootstrap-YYYY-MM-DDTHH-MM-SSZ.json
|
|
```
|
|
|
|
The bootstrap flow requires both services to stop, copies the bundle into the
|
|
private data volume, assigns it to the runtime UID, creates a safety backup,
|
|
verifies that staging is unused, imports the entire development database and
|
|
uploads, deletes the volume copy, and restarts both services. Delete the host
|
|
transfer copy after verifying the imported record counts and critical entities.
|
|
The copy under the local ignored backup directory remains the recovery source.
|
|
|
|
Because this is a full initial clone, development session tokens and account
|
|
data also enter staging. Staging and development remain independent from that
|
|
point forward.
|
|
|
|
## Pulling canonical ISAC data into development
|
|
|
|
Use the manually triggered **Export staging ISAC data** workflow in Gitea
|
|
Actions. It briefly stops staging database writers, exports only canonical
|
|
builder-source tables plus uploaded game-data icons, restarts staging, and
|
|
publishes a `shd-stage-isac-data` artifact for 14 days.
|
|
|
|
Download `isac-stage.json` from that workflow run, stop this project's
|
|
development server, and run:
|
|
|
|
```sh
|
|
npm run isac:import -- /path/to/isac-stage.json --confirm-replace-isac-data
|
|
```
|
|
|
|
The importer automatically creates both a complete development database backup
|
|
and an ISAC-only snapshot before replacing canonical data. It does **not**
|
|
replace local users, roles, sessions, builds, guides, comments, notifications,
|
|
proposals, contributor agreements, audit history, or system-message settings.
|
|
Restart development only after the command reports both backup paths and the
|
|
imported row/icon counts.
|
|
|
|
For a local read-only snapshot without importing:
|
|
|
|
```sh
|
|
npm run isac:export
|
|
```
|
|
|
|
## Manual staging operations
|
|
|
|
From the deployment directory:
|
|
|
|
```sh
|
|
docker compose --env-file .env.deploy -f compose.staging.yml ps
|
|
docker compose --env-file .env.deploy -f compose.staging.yml logs -f app
|
|
docker compose --env-file .env.deploy -f compose.staging.yml stop scheduler app
|
|
docker compose --env-file .env.deploy -f compose.staging.yml run --rm --no-deps app node scripts/db-backup.mjs
|
|
docker compose --env-file .env.deploy -f compose.staging.yml up -d app scheduler
|
|
```
|
|
|
|
Do not use `docker compose down --volumes` for staging. That deletes the named
|
|
volume containing the embedded database and uploads.
|
|
|
|
## Production promotion
|
|
|
|
Production uses a separate compose project, volume, database filename, runtime
|
|
environment, port, and deployment workflow:
|
|
|
|
- Public origin: `https://shd.build`
|
|
- Reverse-proxy upstream: host port `3102`
|
|
- Deployment directory: `/opt/shd-build-production`
|
|
- Database: `/app/data/shd-build-production.db`
|
|
- Persistent volume: `shd-build-production-data`
|
|
|
|
Routine pushes preserve production data unless an included migration removes a
|
|
retired prototype format. The production deploy stops its application and
|
|
scheduler, creates a timestamped JSON backup, applies repeatable migrations,
|
|
restarts, and verifies database health. Keep `docker compose down --volumes`
|
|
away from production.
|
|
|
|
Add these repository Action secrets:
|
|
|
|
- `INFISICAL_PRODUCTION_APP_CLIENT_SECRET`
|
|
- `INFISICAL_PRODUCTION_SCHEDULER_CLIENT_SECRET`
|
|
|
|
Add the matching repository variables:
|
|
|
|
- `INFISICAL_PRODUCTION_APP_CLIENT_ID`
|
|
- `INFISICAL_PRODUCTION_SCHEDULER_CLIENT_ID`
|
|
|
|
Optional repository variables are `PRODUCTION_DEPLOY_PATH` (default
|
|
`/opt/shd-build-production`) and `PRODUCTION_PORT` (default `3102`). The
|
|
`deploy-production.yml` workflow deploys production on pushes to `main` and
|
|
manual dispatches using the trusted `stage-deploy` host runner. Its Gitea
|
|
workflow run number supplies the production `SHD_BUILD_NUMBER`.
|
|
|
|
Prepare the host directory once from an interactive SSH session:
|
|
|
|
```sh
|
|
sudo install -d -o act_runner -g act_runner -m 0750 /opt/shd-build-production
|
|
```
|
|
|
|
Store a separate production `SCHEDULER_TOKEN` under Infisical path
|
|
`/shared/scheduler`. The application and scheduler fetch the same value from
|
|
that path. Do not reuse the staging token.
|
|
|
|
### Initial production bootstrap
|
|
|
|
The production bootstrap clones current development data and uploads. Stop this
|
|
project's development server before creating the bundle:
|
|
|
|
```sh
|
|
npm run production:bootstrap:export
|
|
```
|
|
|
|
Copy the resulting ignored JSON bundle to the deployment host over SSH, then:
|
|
|
|
```sh
|
|
/opt/shd-build-production/bootstrap-production.sh \
|
|
/secure/path/shd-production-bootstrap-YYYY-MM-DDTHH-MM-SSZ.json
|
|
```
|
|
|
|
The bootstrap script requires both services to stop, stages the bundle in the
|
|
private data volume with runtime-only access, and removes that copy after the
|
|
import. The importer verifies the bundle target, creates a production safety
|
|
backup, and refuses to replace a production database containing users, builds,
|
|
audit events, or proposals. Remove the remote host copy after verification.
|
|
|
|
### Promote one News article
|
|
|
|
Export a locally authored News article by ID or slug while the development
|
|
server is stopped:
|
|
|
|
```sh
|
|
npm run article:export -- <article-id-or-slug>
|
|
```
|
|
|
|
The ignored bundle contains the article draft, immutable revisions, search row,
|
|
managed-image metadata, and image bytes. It excludes comments, reports, user
|
|
records, Builds, and ISAC data.
|
|
|
|
After deploying the matching application schema, stop the production app and
|
|
scheduler. Copy the transferred bundle into `/app/data` with `docker cp`, assign
|
|
it to `shd-runtime:node` with mode `0600` through a root one-off container, then
|
|
run `scripts/article-transfer.mjs import` with `ARTICLE_IMPORT_ENABLED=true` and
|
|
`--confirm-article-import`. The importer creates a production database backup
|
|
before opening the database, resolves the article owner by stable ID or
|
|
username, rejects ID and slug conflicts, verifies image checksums, and appends
|
|
`editorial_content / news.promoted`. Remove both bundle copies, restart the app
|
|
and scheduler, and health-check both services.
|
|
|
|
### Pull production ISAC data into development
|
|
|
|
Manually run **Export production ISAC data** in Gitea Actions and download its
|
|
`shd-production-isac-data` artifact. Stop the local development server, then:
|
|
|
|
```sh
|
|
npm run isac:import -- /path/to/isac-production.json --confirm-replace-isac-data
|
|
```
|
|
|
|
The importer creates a complete local database backup and an ISAC-only snapshot
|
|
before replacing canonical game data. It leaves accounts, content, proposals,
|
|
notifications, settings, and audit history untouched.
|
|
|
|
### Pull production ISAC data into staging
|
|
|
|
Manually run **Replace staging ISAC data from production** in Gitea Actions. The
|
|
workflow exports a consistent production snapshot, restarts production, then
|
|
stops staging and imports it. The staging importer creates full and ISAC-only
|
|
safety backups before replacement. Deployments do not run this flow.
|
|
|
|
### Promote managed images without replacing other data
|
|
|
|
Image Library assets use stable IDs and can be promoted additively from local
|
|
development. Export a bundle after finishing local uploads and metadata edits:
|
|
|
|
Set the server-only `REMOVE_BG_API_KEY` under `/app` in each Infisical
|
|
environment that accepts ISAC uploads. The server sends it only in the
|
|
`X-Api-Key` header. Transparent icon uploads skip remove.bg. Opaque sources use
|
|
its preview output, then pass through local trimming, white normalization, WebP
|
|
conversion, and storage.
|
|
|
|
The application stores a remove.bg capacity snapshot and refreshes it from the
|
|
internal scheduler. `REMOVE_BG_CREDIT_CHECK_HOURS` controls the account lookup
|
|
interval and defaults to six hours. Low-capacity alerts require both remaining
|
|
pools to cross their thresholds: `REMOVE_BG_LOW_CREDIT_THRESHOLD` defaults to
|
|
five paid credits, and `REMOVE_BG_LOW_FREE_CALL_THRESHOLD` defaults to ten free
|
|
calls. Alerts are mandatory ISAC notifications for active Administrators and are
|
|
deduplicated for each warning or critical episode.
|
|
|
|
```sh
|
|
npm run images:export
|
|
```
|
|
|
|
The command writes a timestamped JSON bundle under `data/backups/`. It contains
|
|
only managed-image metadata and processed WebP files. Transfer that bundle to
|
|
the deployment host, stop the target `app` and `scheduler` services so the
|
|
embedded database is unlocked, and run the merge using the target image:
|
|
|
|
```sh
|
|
# Staging
|
|
cd /opt/shd-build-stage
|
|
docker compose --env-file .env.deploy -f compose.staging.yml stop scheduler app
|
|
docker compose --env-file .env.deploy -f compose.staging.yml create app
|
|
app_container=$(docker compose --env-file .env.deploy -f compose.staging.yml ps --all --quiet app)
|
|
docker cp /secure/path/managed-images.json "$app_container:/app/data/.managed-images-import.json"
|
|
docker compose --env-file .env.deploy -f compose.staging.yml run --rm --no-deps \
|
|
--entrypoint sh --user root app -c \
|
|
'chown shd-runtime:node /app/data/.managed-images-import.json && chmod 600 /app/data/.managed-images-import.json'
|
|
docker compose --env-file .env.deploy -f compose.staging.yml run --rm --no-deps app \
|
|
node scripts/managed-images-import.mjs /app/data/.managed-images-import.json --confirm-merge
|
|
docker compose --env-file .env.deploy -f compose.staging.yml run --rm --no-deps \
|
|
--entrypoint rm --user root app -f /app/data/.managed-images-import.json
|
|
docker compose --env-file .env.deploy -f compose.staging.yml up -d app scheduler
|
|
|
|
# Production
|
|
cd /opt/shd-build-production
|
|
docker compose --env-file .env.deploy -f compose.production.yml stop scheduler app
|
|
docker compose --env-file .env.deploy -f compose.production.yml create app
|
|
app_container=$(docker compose --env-file .env.deploy -f compose.production.yml ps --all --quiet app)
|
|
docker cp /secure/path/managed-images.json "$app_container:/app/data/.managed-images-import.json"
|
|
docker compose --env-file .env.deploy -f compose.production.yml run --rm --no-deps \
|
|
--entrypoint sh --user root app -c \
|
|
'chown shd-runtime:node /app/data/.managed-images-import.json && chmod 600 /app/data/.managed-images-import.json'
|
|
docker compose --env-file .env.deploy -f compose.production.yml run --rm --no-deps app \
|
|
node scripts/managed-images-import.mjs /app/data/.managed-images-import.json --confirm-merge
|
|
docker compose --env-file .env.deploy -f compose.production.yml run --rm --no-deps \
|
|
--entrypoint rm --user root app -f /app/data/.managed-images-import.json
|
|
docker compose --env-file .env.deploy -f compose.production.yml up -d app scheduler
|
|
```
|
|
|
|
Each import creates a full target database backup before changes. The importer
|
|
adds new stable IDs, skips identical assets, and blocks conflicting records.
|
|
After reviewing an intentional metadata or file replacement, add
|
|
`--update-existing`. Imported changes append `image.imported` or
|
|
`image.import_updated` audit events. Remove the transferred bundle after
|
|
verifying both environments because it may contain unpublished visual assets and
|
|
creator identifiers.
|
|
|
|
The staging and production npm import commands fetch `/app` from Infisical. Run
|
|
them only where the target database and storage paths are mounted:
|
|
|
|
```sh
|
|
npm run images:import:staging -- /path/to/bundle.json --confirm-merge
|
|
npm run images:import:production -- /path/to/bundle.json --confirm-merge
|
|
```
|