npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ossy/deployment-tools

v3.18.0

Published

Collection of scripts and tools to aid deployment of containers and static files to Amazon Web Services through GitHub Actions

Downloads

2,019

Readme

@ossy/deployment-tools

Infrastructure and CDK tooling for the Ossy platform.

Architecture (epic #553): CloudFront → WAF → ALB → ECS Fargate, with per-service Secrets Manager and GitHub Actions rollouts via OIDC. EC2 + Caddy + EIP are removed from CDK (#560).

Architecture

Internet → CloudFront (ACM TLS, us-east-1) → WAF → ALB (:80)
             ├── host rules from platforms.json `services[].hosts|domain`
             │     e.g. ossy.se → website-ossy Fargate service
             ├── other known hosts / *.ossy.se → platform-runtime Fargate service
             └── default action → platform-runtime
  • Website images include the API — no separate ossy-api service.
  • Task definitions inject env from Secrets Manager ({platform}/{serviceKey}) and pull images from GHCR using {platform}/ghcr-pull.
  • Health checks use GET /health on each ALB target group and each ECS container (Node fetch probe; no curl in node:*-slim).
  • App CloudFront is separate from the /media CloudFront in storage-static.
  • Viewer Host is forwarded to the ALB (OriginRequestPolicy.ALL_VIEWER) so host rules match.
  • ACM terminates TLS at CloudFront; Caddy is not part of this path.
  • Route53 for known domains aliases to CloudFront (A + AAAA), not an Elastic IP.

Customer nameserver / custom-domain handoff at a registrar is out of scope (follow-up).

Health checks (ALB / ECS)

Every deployable Ossy HTTP image exposes an unauthenticated liveness probe at GET /health (also accepts HEAD). It returns 200 when the Node process can accept traffic. The JSON service field prefers OSSY_SERVICE_NAME (set on the task definition to the service key).

| Image | How /health is provided | |---|---| | ghcr.io/ossy-se/platform (runtime) | Built into @ossy/platform startRuntime — does not load a CMS site | | Website / app images (website-ossy, other services[]) | Built into @ossy/platform startServer (and/or a local health.api.js until the platform package is bumped) |

ALB target groups probe GET /health. ECS container health checks run an exec-form Node one-liner (ecsContainerHealthCheckCommand) that fetches the same path on PORT and aborts after HEALTHCHECK_FETCH_TIMEOUT_MS (4s). Do not point probes at /, /status, or authenticated routes.

Task definitions also set plain env PORT, OSSY_SERVICE_NAME, NODE_ENV, media bucket vars, and GEOLITE2_COUNTRY_MMDB (/tmp/GeoLite2-Country.mmdb, #804) via containerEnvironment. Those keys are filtered out of Secrets Manager injection (secretEnvKeysForEcsContainer) so ECS cannot reject duplicate environment/secret keys.

Configuration — platforms.json

packages/infrastructure/platforms.json is the single source of truth for a platform. Each entry becomes a CDK Stage with its own set of stacks.

| Field | Required | Description | |---|---|---| | platformName | Yes | Unique name, used as the CDK stage name and CloudFormation stack prefix | | awsAccountId | Yes | AWS account ID | | awsRegion | No | Defaults to eu-north-1 | | sesDomains | No | Domains to configure SES email identity for | | domains | No | Domains for Route53 aliases → CloudFront and ACM coverage. Supports wildcards (e.g. *.ossy.se). | | services | No | Per-platform HTTP container services (see below) | | dnsRecords | No | Additional DNS records by root domain (currently supports MX) | | env | No | Env vars upserted into per-service Secrets Manager secrets (npm run sync-secrets). Never uploaded to S3. Values remain in git for now. | | githubDeployRepos | Yes (for platform-ci) | owner/repo list trusted by the GitHub OIDC deploy role. Set in platforms.json (e.g. ossy-se/ossy, ossy-se/website-ossy, ossy-se/website-plexus-sanitas). | | awsKeyPairName / awsInstanceClass / awsInstanceSize | No | Unused after EC2 decommission (#560). Harmless if left in JSON. |

services — HTTP containers on ECS

HTTP services (default) become Fargate services with ALB host rules. The container must listen on port 3000.

"services": [
  {
    "name": "ossy-website-ossy",
    "domain": "ossy.se",
    "image": "ghcr.io/ossy-se/website-ossy:latest",
    "hosts": ["ossy.se"]
  }
]

| Field | Description | |---|---| | name | Unique service name — Secrets Manager / ECS key strips a leading ossy- (website-ossy). | | domain | Primary hostname (default ALB host when hosts is omitted). | | hosts | Optional ALB host-header list. Example: ["ossy.se"] so the website image serves the apex. Defaults to [domain]. | | image | Docker image to pull and run (GHCR). |

Built-in (always present):

  • runtime — platform runtime (ghcr.io/ossy-se/platform:latest) serving CMS-published apps; ALB default + unclaimed domains.

Not deployed: separate ossy-api container; TCP/UDP services[] entries (ignored by the ECS path).

Wildcard subdomains: Add *.ossy.se to domains for a Route53 wildcard alias to CloudFront.

MEDIA_REPOSITORY and MEDIA_CDN_DOMAIN_NAME are derived from the provisioned S3 bucket and media CloudFront — do not set them in env.

To add a new platform, add an entry to platforms.json. No bucket name or CDN domain is needed; these are auto-generated by CloudFormation.

Platform secrets (Secrets Manager)

Each platform service gets one Secrets Manager secret (JSON object of env keys). Values still live in platforms.json for now; CDK owns the secret resources and IAM, and deploy upserts values.

| Secret name | Source | |---|---| | {platform}/runtime | Built-in platform-runtime | | {platform}/website-ossy | HTTP entry in services[] named ossy-website-ossy (leading ossy- stripped) | | {platform}/… | Other HTTP services[] entries |

Not created: ossy-api. TCP/UDP services[] entries are skipped.

Each secret has a matching IAM role {platform}-{key}-task assumed by ecs-tasks.amazonaws.com with read access to that secret only.

Also created: {platform}/ghcr-pull — Docker Hub-style credentials JSON (username + password PAT with read:packages) for private GHCR pulls. Not filled by sync-secrets; set manually once after the secrets stack deploys.

Sync values after CDK deploy

cd packages/deployment-tools

# Create / update secret resources + IAM (part of the stage)
npx cdk deploy 'ossybot/platform-secrets' --profile ossybot

# Upsert secret *values* from platforms.json (never prints secret contents)
npm run sync-secrets -- --profile ossybot

# Optional: one platform, or dry-run
npm run sync-secrets -- --profile ossybot --platform ossybot
npm run sync-secrets -- --dry-run

Do not remove secrets from platforms.json yet.

ECS Fargate + ALB

cd packages/deployment-tools

# Secrets + IAM first, then sync values + set ghcr-pull
npx cdk deploy 'ossybot/platform-secrets' --profile ossybot
npm run sync-secrets -- --profile ossybot
# then put GHCR credentials into ossybot/ghcr-pull

# VPC, cluster, Fargate services, ALB host rules
npx cdk deploy 'ossybot/platform-ecs' --profile ossybot

| Resource | Notes | |---|---| | VPC | Public + private subnets, 1 NAT gateway | | ECS cluster | {platform}-platform | | Fargate services | runtime (ghcr.io/ossy-se/platform:latest) + each HTTP services[] entry | | ALB | Internet-facing HTTP :80; host rules; default → runtime; health check /health | | Task env | Secrets Manager JSON keys from platforms.json env; MEDIA_* from storage stack |

CloudFront + WAF + ACM

App traffic uses its own CloudFront distribution (do not overload the /media CDN). The edge stack is deployed in us-east-1 because CloudFront viewer certificates and CLOUDFRONT-scope WAFv2 web ACLs must live there.

# After platform-ecs exists (ALB DNS used as origin)
npx cdk deploy 'ossybot/platform-edge' --profile ossybot

# DNS aliases for known domains → CloudFront
npx cdk deploy 'ossybot/dns' --profile ossybot

| Piece | Detail | |---|---| | Origin | ALB DNS name from platform-ecs (LoadBalancerDnsName), HTTP :80 | | Certificate | ACM in us-east-1, DNS-validated; covers domains + HTTP service hosts (apex + *.ossy.se etc.) | | WAF | WAFv2 web ACL (AWSManagedRulesCommonRuleSet + KnownBadInputs) associated via webAclId | | Cache | CACHING_DISABLED + ALL_VIEWER origin request policy (forwards Host) | | DNS | Route53 alias A/AAAA for known domains → CloudFront |

Release to ECS from GitHub Actions

Images stay on GHCR. After a push to main, Publish workflows tag the image with the git SHA (and :latest), assume the platform deploy role via GitHub OIDC (no long-lived AWS keys), register a new task definition revision with the SHA tag, and update the ECS service.

# After platform-ecs exists — creates OIDC provider + deploy role
npx cdk deploy 'ossybot/platform-ci' --profile ossybot

| Piece | Detail | |---|---| | Role | {platform}-github-deploy (e.g. ossybot-github-deploy) | | Trust | OIDC token.actions.githubusercontent.com, sub = repo:<owner>/<repo>:ref:refs/heads/main for each entry in platforms.json → githubDeployRepos | | Cluster / services | {platform}-platform / {platform}-{serviceKey} (e.g. ossybot-runtime, ossybot-website-ossy) | | Image tags | ghcr.io/ossy-se/<image>:latest and :<git-sha> | | Workflows | each trusted repo’s Publish workflow rolls its ECS service |

Add another deploy source by appending owner/repo to githubDeployRepos and redeploying platform-ci. Deploy that stack once before the first Actions rollout. Workflows wait for ECS service stability after update.

CDK Stacks

Each platform is a CDK Stage containing these stacks:

| Stack | Description | |---|---| | storage-static | S3 bucket (auto-named), daily backup plan, CloudFront CDN for /media. | | platform-secrets | Secrets Manager secrets per service + ECS task roles (read IAM) + ghcr-pull. Values synced from platforms.json via npm run sync-secrets. | | platform-ecs | VPC, ECS Fargate services, ALB with host-based routing. | | platform-ci | GitHub OIDC provider + {platform}-github-deploy IAM role for ECS rollouts from Actions. | | platform-edge | CloudFront + WAF + ACM in us-east-1 in front of the ALB. | | dns | Route53 alias records for known domains → CloudFront app distribution. | | ses | SES email sending identity and DKIM records. |

Cutover / decommission EC2 (#560)

CDK no longer synthesizes deployment-target (EC2 + EIP + Caddy + systemd units). After platform-edge and dns are live and traffic is healthy:

# Destroy the leftover CloudFormation stack (name may be stage-prefixed)
npx cdk destroy 'ossybot/deployment-target' --profile ossybot
# or, if the stack still exists outside the app:
# aws cloudformation delete-stack --stack-name ossybot-deployment-target --profile ossybot

Downtime during cutover is acceptable. Confirm known domains resolve to CloudFront before destroying EC2.

Bootstrapping a new platform

cd packages/deployment-tools
npx cdk deploy 'ossybot/**' --profile ossybot --require-approval never
npm run sync-secrets -- --profile ossybot
# set {platform}/ghcr-pull credentials once

Deploying changes

Target a single platform:

npx cdk deploy 'ossybot/**' --profile ossybot

Target all platforms:

npx cdk deploy '**' --profile ossybot

Preview changes without deploying:

npx cdk diff 'ossybot/**' --profile ossybot

List all stacks:

npx cdk list

Redeploying after a code change

Merge to main — Publish workflows push GHCR tags (:latest + :<sha>) and roll the matching ECS service. No SSH / systemctl.

Environment variables

Defined in the env block of platforms.json, synced into Secrets Manager. The following are injected automatically by the ECS stack and should not be set manually:

| Variable | Source | |---|---| | MEDIA_REPOSITORY | S3 bucket name from storage-static stack | | MEDIA_CDN_DOMAIN_NAME | CloudFront domain from storage-static stack |

Other common env vars:

| Variable | Used by | Description | |---|---|---| | DB_URL | API | Atlas MongoDB connection string | | DB_NAME | API | MongoDB database name | | TOKEN_SECRET | API | JWT signing secret | | AWS_ACCESS_KEY_ID | API | AWS credentials for S3 | | AWS_SECRET_ACCESS_KEY | API | AWS credentials for S3 | | OSSY_API_KEY | Platform, websites | API JWT for CMS reads | | OSSY_COOKIE_SECRET | Websites | Cookie signing secret | | GEOLITE2_COUNTRY_MMDB | App (@ossy/analytics location projection) | Absolute path to MaxMind GeoLite2-Country .mmdb (#769 / #804). Set on every ECS task as /tmp/GeoLite2-Country.mmdb. startServer downloads the file there when MAXMIND_LICENSE_KEY is set. Optional; without the file countryCode is omitted (IP still stored on the event). | | MAXMIND_LICENSE_KEY | App (startServer GeoLite2 download) | MaxMind license key. Add to platforms.json env and npm run sync-secrets so every website image (ossy.se, plexus-sanitas, …) can download GeoLite2-Country at boot (#804). Free signup: https://www.maxmind.com/en/geolite2/signup | | MAXMIND_ACCOUNT_ID | App (startServer GeoLite2 download) | Optional MaxMind account id. When set with the license key, download uses the permalink + HTTP Basic auth API. | | OSSY_TRUST_PROXY_HOPS | Platform / websites | Express trust proxy hop count behind CloudFront→ALB (default: 2). | | OPENROUTER_API_KEY | App / API (@ossy/media-tasks) | OpenRouter API key for LLM tasks (e.g. visual-content-descriptors). Add to platforms.json env and run npm run sync-secrets. Optional: OPENROUTER_SITE_URL, OPENROUTER_APP_TITLE, OPENROUTER_BASE_URL. |

Useful commands

cd packages/deployment-tools
npm test
npx cdk list
npx cdk diff 'ossybot/**' --profile ossybot
npm run sync-secrets -- --profile ossybot --dry-run