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

fargate-deployer

v1.1.1

Published

Deploy containers to AWS ECS Fargate from a declarative manifest — long-running services behind an ALB, or EventBridge-scheduled tasks.

Readme

Fargate Deployer

License: MIT

Deploy a container to AWS ECS Fargate from a single declarative manifest — a long-running service behind an Application Load Balancer, or a set of EventBridge-scheduled tasks.

- uses: futuremoney/fargate-deployer@v1
  with:
    manifest: deploy/production.yaml
    role-to-assume: arn:aws:iam::111122223333:role/github-actions-deploy

That one step builds your image, pushes it to ECR, deploys the stack, and waits until ECS reports the service healthy. Your repository needs a Dockerfile and a manifest — no CDK app, no Node.js toolchain, no CloudFormation templates, no aws ecs update-service scripting.


Contents


Why this exists

Most teams running Fargate already own the expensive, long-lived pieces: a VPC, an ECS cluster, one or two load balancers, a wildcard certificate. What they deploy over and over is the cheap part — a task definition, a service, a target group, a listener rule.

Existing options make that repetitive part harder than it should be. Writing CDK or Terraform per service means every team maintains infrastructure code to express the same six resources. aws ecs update-service scripting handles a new image but not a new port, health check or environment variable. Full-blown platforms want to own your VPC.

This action takes the middle path: you own the shared infrastructure, it owns the per-service resources. You describe the service in about twenty lines of YAML, and it deploys the same way every time, in any account, for anybody.

It is a public, generalised version of an internal deployer that has shipped production services for a couple of years. Everything account-specific that used to be hardcoded is now a manifest field.

Quickstart

1. Bootstrap the account, once

The deployer runs on AWS CDK, which needs a one-time bootstrap per account and region:

npx cdk bootstrap aws://111122223333/us-east-1

2. Create a deploy role

Set up GitHub OIDC and a role your workflow can assume — no long-lived AWS keys in your repository. docs/aws-setup.md has the trust policy, the permissions policy, and a copy-pasteable CloudFormation template.

3. Write a manifest

deploy/production.yaml:

kind: Service
name: hello-api
account: "111122223333"
region: us-east-1

cluster:
  name: my-cluster

network:
  vpcId: vpc-0abc123def4567890
  subnets:
    - subnet-0abc123def4567890
    - subnet-0fed987cba6543210

task:
  cpu: 256
  memory: 512
  containerPort: 8080
  environment:
    NODE_ENV: production
  secrets:
    DATABASE_URL: "arn:aws:secretsmanager:us-east-1:111122223333:secret:prod/db-AbCdEf:url::"

loadBalancer:
  listenerArn: arn:aws:elasticloadbalancing:us-east-1:111122223333:listener/app/my-alb/1234567890abcdef/abcdef1234567890
  securityGroupId: sg-0abc123def4567890
  hostHeaders: hello.example.com
  healthCheck:
    path: /health

Check it before you push anything — this touches no AWS APIs:

npx fargate-deployer validate --manifest deploy/production.yaml

4. Add the workflow

.github/workflows/deploy.yml:

name: Deploy
on:
  push:
    branches: [main]

permissions:
  contents: read
  id-token: write # required for OIDC

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: futuremoney/fargate-deployer@v1
        with:
          manifest: deploy/production.yaml
          role-to-assume: arn:aws:iam::111122223333:role/github-actions-deploy

Push to main. More patterns — multiple environments, promoting an image from staging to production, pull-request diffs — are in examples/workflows/.

What it creates, and what you bring

The split is deliberate. Anything shared between services, expensive to replace, or dangerous to delete stays yours.

| You bring (referenced by ARN or ID) | It creates (owned by the stack) | | --- | --- | | VPC and subnets | ECS task definition | | ECS cluster | ECS service, or EventBridge rules | | Application Load Balancer and listener | ALB target group and listener rule | | ACM certificate | CloudWatch log group | | Secrets Manager secrets / SSM parameters | Task security group (if you don't supply one) | | IAM roles (optional) | Task and execution IAM roles (if you don't supply them) | | | Auto-scaling target and policies |

Everything in the right column is deleted cleanly when you delete the stack. Nothing in the left column is ever modified, with one deliberate exception: when you give loadBalancer.securityGroupId, an ingress rule is added so the load balancer can reach your tasks. Set manageSecurityGroupRules: false to manage that yourself.

Roles and security groups are the accessibility knob. Omit them and you get working defaults with the exact permissions the task needs — image pull, log writes, read access to precisely the secrets you listed, and ECS Exec. Supply them and the deployer treats them as immutable and touches nothing.

Action inputs

Only manifest is required.

What to deploy

| Input | Default | Description | | --- | --- | --- | | manifest | — | Required. Path to the manifest, e.g. deploy/production.yaml. | | image | build one | Deploy this exact image instead of building. Skips build and push entirely. | | command | deploy | deploy, diff, synth, validate or destroy. |

AWS authentication

| Input | Default | Description | | --- | --- | --- | | role-to-assume | — | IAM role ARN to assume via GitHub OIDC. Recommended. Needs permissions: id-token: write. | | role-session-name | fargate-deployer | Session name for the assumed role. | | role-external-id | — | External ID, if the role's trust policy requires one. | | role-duration-seconds | 3600 | Session lifetime. Raise it if a slow rollout can outlast an hour. | | aws-access-key-id | — | Static access key, when OIDC is not available. | | aws-secret-access-key | — | Secret key that goes with it. | | aws-session-token | — | Required when the key and secret are temporary STS credentials. | | aws-region | from manifest | Override the region. |

Credentials go in with:, not secrets: — an action has no secrets: block. The values are still masked in logs. If you would rather pass them as secrets:, call the reusable workflow instead. A worked example using access keys is in examples/workflows/iam-access-keys.yml.

Four combinations are supported, and one non-combination:

| What you set | What happens | | --- | --- | | role-to-assume | OIDC. No stored credentials. Recommended. | | aws-access-key-id + aws-secret-access-key | Static IAM user credentials. | | …plus aws-session-token | Temporary STS credentials. | | …plus role-to-assume | Assume that role from those keys — the usual pattern when a low-privilege CI user chains into a deploy role. | | Nothing | The credential step is skipped, and whatever the job already has is used — credentials set by an earlier step, or a self-hosted runner's instance profile. |

Image build

| Input | Default | Description | | --- | --- | --- | | ecr-repository | manifest name | ECR repository to push to. | | create-ecr-repository | true | Create the repository if it does not exist. | | image-tag | github.sha | Tag for the built image. | | push-latest | false | Also tag the image latest. | | dockerfile | Dockerfile | Path to the Dockerfile, relative to context. | | context | . | Docker build context. | | build-args | — | KEY=VALUE per line. Visible in image history — not for secrets. | | build-secrets | — | KEY=VALUE per line, read in the build via RUN --mount=type=secret,id=KEY. | | platforms | from manifest | Build platform. Follows task.runtimePlatform.cpuArchitecture. | | provenance | false | Attach SLSA provenance. Off because the resulting image index is not usable by ECS. |

Deployment behaviour

| Input | Default | Description | | --- | --- | --- | | bootstrap | false | Run cdk bootstrap first. Useful for a brand-new account; turn it off afterwards. | | wait-for-stability | true | Wait for ECS to report the service stable, and print recent service events on failure. | | cdk-args | — | Extra flags passed straight to the CDK CLI. | | working-directory | . | Directory that manifest and context are relative to. | | node-version | 20 | Node.js used to run the deployer itself, not your build. |

Action outputs

| Output | Description | | --- | --- | | image | Full image URI that was deployed | | image-digest | Digest of the pushed image | | stack-name | CloudFormation stack name | | service-name | ECS service name (Service manifests) | | cluster | ECS cluster deployed into | | region / account | Target region and account from the manifest | | kind | Service or ScheduledTasks | | log-group | CloudWatch log group the tasks write to | | alb-dns-name | Load balancer DNS name — the CNAME target for your DNS records | | alb-hosted-zone-id | Load balancer hosted zone ID, for a Route 53 alias record |

Pointing DNS at the service

After a deploy the job summary lists the record to create for each host header you routed:

DNS

Point each hostname at the load balancer:

| Record | Type | Value | |---|---|---| | hello.example.com | CNAME | my-alb-1234567890.us-east-1.elb.amazonaws.com |

An apex domain cannot hold a CNAME — use a Route 53 alias A record to hosted zone Z35SXDOTRQ7X7K instead.

The same values are available as outputs, so the records can be created by the workflow rather than by hand:

- id: deploy
  uses: futuremoney/fargate-deployer@v1
  with:
    manifest: deploy/production.yaml
    role-to-assume: arn:aws:iam::111122223333:role/github-actions-deploy

- name: Upsert the DNS record
  run: |
    aws route53 change-resource-record-sets \
      --hosted-zone-id "$MY_ZONE" \
      --change-batch '{
        "Changes": [{
          "Action": "UPSERT",
          "ResourceRecordSet": {
            "Name": "hello.example.com",
            "Type": "A",
            "AliasTarget": {
              "HostedZoneId": "${{ steps.deploy.outputs.alb-hosted-zone-id }}",
              "DNSName": "${{ steps.deploy.outputs.alb-dns-name }}",
              "EvaluateTargetHealth": true
            }
          }
        }]
      }'

Resolving the name costs one elasticloadbalancing:DescribeLoadBalancers call after the deploy. If the deploy role lacks that permission the step warns and the outputs are empty — the deploy itself still succeeds.

Scheduled tasks

Change kind and the same action deploys cron jobs instead — one EventBridge rule per entry, each starting a Fargate task:

kind: ScheduledTasks
name: data-sync
account: "111122223333"
region: us-east-1

cluster: { name: my-cluster }
network:
  vpcId: vpc-0abc123def4567890
  subnets: [subnet-0abc123def4567890]

task:
  cpu: 256
  memory: 512

tasks:
  - name: nightly-sync
    schedule: cron(0 6 * * ? *) # 06:00 UTC
    command: ["node", "dist/jobs/sync.js"]

  - name: hourly-prices
    schedule: rate(1 hour)
    command: ["node", "dist/jobs/prices.js"]

See docs/scheduled-tasks.md for schedule syntax, per-job overrides, and how to disable a job without deleting it.

Calling it as a reusable workflow

The action is the primary interface, but a workflow_call wrapper ships alongside it for callers who prefer that shape — most often because AWS credentials already arrive through a secrets: block, which an action cannot accept:

jobs:
  deploy:
    uses: futuremoney/fargate-deployer/.github/workflows/fargate-deploy.yml@v1
    with:
      manifest: deploy/development.yaml
    secrets:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_DEVELOPMENT }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY_DEVELOPMENT }}

It brings its own job, so there is no runs-on, no steps, and no actions/checkout. Extra inputs it adds over the action: environment (to apply a GitHub environment's protection rules) and runner. Extra secrets: AWS_SESSION_TOKEN and BUILD_SECRETS.

Two things to know. Reusable workflows cannot be listed on the Marketplace — only actions can — so this is referenced by path rather than by name. And it adds a job boundary, so permissions and environment apply to the whole called workflow rather than to one step.

See examples/workflows/reusable-workflow-with-secrets.yml.

Running it outside GitHub Actions

The action is a thin wrapper around a CLI, so the exact command CI runs also runs on your laptop, in GitLab CI, or from a Makefile:

npx fargate-deployer validate --manifest deploy/production.yaml
npx fargate-deployer diff     --manifest deploy/production.yaml --image my-image:tag
npx fargate-deployer deploy   --manifest deploy/production.yaml --image my-image:tag
npx fargate-deployer destroy  --manifest deploy/production.yaml --image my-image:tag

It uses your ambient AWS credentials, the same as any other AWS CLI tool.

Running diff locally before pushing is the fastest way to see what a deploy would change — it is the same synthesis CI performs, so the output matches.

Using it inside your own CDK app

If your repository already owns CDK infrastructure, import the stack into the app you have rather than deploying a second one:

import * as cdk from 'aws-cdk-lib';
import { createStack, loadManifest } from 'fargate-deployer';

const app = new cdk.App();
const infra = new MyInfraStack(app, 'my-infra');

const config = loadManifest('deploy/production.yaml');
config.task.environment.QUEUE_URL = infra.queue.queueUrl; // a CDK token

createStack({ app, config, image: process.env.IMAGE! });

cdk deploy --all then covers both, and CDK turns that token into a real cross-stack reference.

This is not just a preference. The CLI synthesises its own single-stack app, so any other stacks in your repository are invisible to it — they do not deploy, and nothing warns you. If you own CDK already, this is the mode you want.

docs/construct-mode.md covers the whole of it.

Documentation

| | | | --- | --- | | Manifest reference | Every field, its default, and when to set it | | AWS setup | Bootstrap, OIDC, IAM policies, prerequisites | | Scheduled tasks | Cron jobs in depth | | Architecture | What is created, how it is named, why | | Construct mode | Using the stacks inside a CDK app you already have | | Troubleshooting | Common failures and what they mean | | Contributing | Development setup and release process | | Security policy | Reporting a vulnerability, and the action's trust boundaries | | Changelog | What changed in each release |

License

MIT