rvn-ec2-service / Latest version: 2.5.0
Dependencies and consumers
Every dependency input can be specified manually to reference existing external infrastructure rather than a Ravion module. Set the dependency input tonull and set its mapped inputs directly; see Use existing infrastructure instead of a module reference.
Readme
Runs supervised workloads on a stable EC2 Auto Scaling Group, with optional shared ALB routing and switchable container or manual in-place deploys.Overview
The EC2 Service module deploys supervisord-managed workloads across an EC2 Auto Scaling Group. Deploys update the existing hosts in place instead of creating a new task or instance for every release. Container mode replaces a Docker container on each host; Manual mode runs host-level preparation commands and then starts a foreground command. You can switch modes without replacing the group. Ravion provisions the launch template, Auto Scaling Group, instance role and security group, SSM deploy document, app log group, and optional target group and listener rule. Web services can attach to a standalone Application Load Balancer or reuse the public or private ALB from an ECS cluster. The selected network and load balancer must use the same AWS account, region, and VPC. Deploys, app restarts, and routine in-place stack updates retain the instances and their root and optional data volumes. Even changing the AMI leaves running instances alone: the change becomes a new launch template version that only applies to instances launched later, because the module does not run an instance refresh. An instance is replaced when you deliberately terminate or recycle it, when the group scales in, or when it fails its Auto Scaling health check (EC2 by default, or load balancer health whenhealth_check_type is set to ELB). Group naming is fixed by default for new and existing services, and routine upgrades preserve the group and SSM document names. For a service that has not yet deployed a release, an advanced operator can set {"autoscaling_group_generated_name_enabled": true} in Advanced Terraform variables to avoid a name collision on replacement. This is not a zero-downtime migration: the deploy manager stores the old group name with the last successful release and does not automatically retarget a newly named group. Enabling or disabling the variable on a running service, or replacing a generated-name group after deployment, can interrupt the app and delete instance-local volumes. Repair tainted state under operator supervision if the existing group is healthy. Otherwise, arrange a separate release migration, backups, and enough EC2 capacity before replacing a live group.
Terraform source: ravionhq/modules/compute/ec2_service
Use cases
Instance disks are a reasonable place for application state, including a local SQLite database, as long as you back it up. Use EFS when several instances must read and write the same files, or when files must be available immediately on a replacement instance without a restore step. Use a managed database when you need relational or transactional data with its own backups, failover, and read scaling.
EC2 Service or ECS?
For a normal stateless containerized web service or worker, prefer an ECS Web Service, ECS Worker Service, or ECS Network Service. ECS gives you immutable task revisions, scheduler-managed replacement, sidecars, Fargate or shared EC2 capacity, and rolling or traffic-shift deployment strategies. Choose EC2 Service only when a concrete host-level requirement outweighs those ECS benefits:
Selecting EC2 capacity for an ECS service does not make it equivalent to this module. ECS on EC2 still deploys replaceable tasks through the ECS scheduler. This module deploys directly to stable hosts and gives the application the whole instance.
Choosing an instance type
Instance type is the sizing decision this module cannot make for you. Make it in two steps: pick the family that fits the workload, then pick the newest generation of that family that the selected account and region actually offer.
In a name like
m8g.large, the digit is the generation and the letters after it identify the processor: g is AWS Graviton (arm64), i is Intel, a is AMD, and older families such as m5 or t3 carry no processor letter. A -flex variant such as m8i-flex costs less for workloads that do not sustain high CPU.
Always take the highest generation number available for the family you chose. Each generation is faster and cheaper per unit of work than the one below it, so m8g beats m7g, which beats m6i. This is the most common configuration mistake: an instance type copied from an example, an older project, or an AI assistant’s memory is usually one or two generations behind, which pays more for less performance. Read the current generation off the Instance type list rather than assuming it. The one exception is burstable: t4g, t3, and t3a are still the newest burstable families, because AWS has not released a newer one.
The Instance type list shows only what the selected account and region support, which is the authoritative answer for that region. Newest generations reach the largest regions first, so a region may top out a generation behind. The list is region-level, so a type offered in the region can still be missing from an individual Availability Zone; if capacity fails in one subnet, spread the group across more AZs. To check from the command line before configuring the module:
arm64 Amazon Linux 2023 AMI from the instance type automatically, so nothing else needs configuring. A Custom AMI ID is used exactly as given, so set one only when it already matches the instance type’s architecture. The requirement is that your container image (Container mode) or host-installed dependencies (Manual mode) build for arm64. Choose an Intel or AMD type when something in the stack is x86_64-only.
Then right-size from measurement rather than guessing: start with the smallest size in the chosen family that holds the working set, watch the service’s CPU and memory charts, and move up a size or enable CPU autoscaling from there.
Changing Instance type later applies to instances launched after the change. Running instances keep their current type until you recycle them, and a Graviton/x86 switch changes the AMI for new instances only, so a group can briefly run both architectures mid-recycle. Container images must work on both architectures during that window, or recycle every instance promptly after the change.
Deploy types
Instances are prepared for both modes at launch. Supervisord owns the long-running process and restarts it after an unexpected exit. In Container mode, Start command override replaces the image CMD but preserves its ENTRYPOINT. In Manual mode, the start command must remain in the foreground and should explicitly drop root privileges when the app should run as another user.
Container workloads that need to orchestrate sibling containers can enable Docker socket mount. The deploy runner bind-mounts
/var/run/docker.sock at the identical path inside the app container and adds the host docker group’s GID at container-start time, so the image must include a docker CLI. This is Docker-outside-of-Docker: mounting the host Docker socket grants the container root-equivalent control of the instance, including the ability to start privileged containers, read the host filesystem, and use the instance role. Leave it disabled unless this level of access is required.
Build sources
Build settings apply only to Container mode. Manual mode disables the build pipeline and does not create an ECR repository. Managed ECR repository lets a pipeline push images to the Ravion-managed repository without a module build config. Built-image repositories scan images on push by default. Registry username/password credentials are not supported. Public images work directly; same-region private ECR requires repository permissions for the generated
<service-name>-instance role.
Worked examples
Host-installed application
Use Manual mode when the release genuinely needs to update the host rather than replace a container. For example, bootstrap anapp user and an initial checkout with Additional user data, then configure:
Container that needs host customization
Choose Container mode with a custom AMI or Additional user data when the app is containerized but also needs host software that ECS does not model for the service. A typical web configuration uses at least two instances, App port3000, Health check path /health, and host or path rules on a shared ALB. Use Dockerfile or Railpack for Ravion builds, Managed ECR repository when an external pipeline should publish to a Ravion-managed repository, or Pull from image registry when an external pipeline already publishes elsewhere.
If the requirement is only “run my container,” use ECS instead. The EC2 choice should come from a real host dependency, stable-host requirement, or manual operational constraint. Access to the Docker socket, a local database on disk with local-disk latency, or shelling into a long-lived host with SSM Session Manager all qualify. In Container mode, only the data volume and EFS mount path are bind-mounted into the app container, so a workload that needs the host’s Docker socket or other host paths belongs in Manual mode.
Worker with local state on disk
Turn Web service off, keep Container mode, and enable a Data volume mounted at a path such as/var/lib/app. Container mode bind-mounts that path into the app container, and its contents stay in place across every deploy and restart. Add a backup for anything you cannot lose, such as an EBS snapshot schedule or a periodic sync to S3, because the volume is deleted if the instance is ever replaced. Choose EFS instead when several instances must share the same files.
Load balancing and worker mode
With Web service enabled, select a shared Ravion Application Load Balancer. The service creates an HTTP instance target group and a listener rule on the ALB’s HTTPS listener when available, otherwise its HTTP listener. Route traffic with Domain host rules, Path rules, or both; when both are empty, the rule matches/*.
Several EC2 services (and other services) can attach to the same load balancer with different host or path rules. Slow start can ramp traffic to newly registered instances, and sticky sessions can use either an ALB-managed cookie or an application cookie.
Allowed CIDR blocks permit direct access to the app port on the instances. This bypasses load balancer TLS termination, WAF, authentication, and access logging, so leave the list empty unless direct access is required.
Turn Web service off for worker groups. No target group, listener rule, ALB health check, or local HTTP deploy gate is configured.
Deploys
Container deploys run through an SSM command document created by the module. On each instance, the deploy:- Rebuilds the app environment file and fetches configured secret values on the instance.
- Pulls the selected image before interrupting the running app.
- For a web service with another registered target, deregisters and drains this instance.
- Stops the prior process, replaces the app container, and starts it under supervisord.
- Polls the local health path for an HTTP response below 400, failing that instance’s deploy otherwise.
- Re-registers a drained instance and waits for the ALB to mark it in service.
Logs and metrics
App stdout and stderr from the supervised process are sent to/ravion/ec2/<service-name> in CloudWatch Logs. Every deployment and instance has a distinct stream named deployment/<deployment-id>/instance/<instance-id>. SSM preparation and deploy-script stdout and stderr are copied into the same instance stream while remaining available with the deployment command.
Supervisord rotates the log file on the instance so a crash-looping process cannot fill the root volume. Log rotation size (MB) and Rotated log files kept bound on-instance usage to (kept + 1) times the rotation size for each deployment log. Ravion writes the supervisord program configuration after Deploy commands run, so rotation cannot be changed from a deploy command; change these fields instead. Newly launched instances receive the same settings from the launch template.
The dashboard shows desired and in-service instance counts for every service. Web services also show request count, target response time, target 4xx and 5xx responses, and healthy and unhealthy host counts. Worker groups omit load-balancer metrics.
Storage and durability
Root and Data volumes are encrypted gp3 EBS volumes attached to the instance. They are durable for the life of the instance: normal in-place deploys and updates do not replace it, so files written to disk stay there at local-disk latency. Replacing the Auto Scaling Group does replace its instances and volumes.
Both volumes are deleted with the instance. Keep backups of critical data on the disk — an EBS snapshot schedule, database dumps to S3, or a periodic sync — rather than avoiding local storage.
Enable EFS and select a Ravion EFS module when several instances must share the same files, or when a replacement instance must find the data already in place. The service mounts the file system through its access point when present and attaches the EFS client security group to each instance. Container mode bind-mounts the Data and EFS host paths into the app container at the same paths. EFS is a network file system, so latency-sensitive local state such as an embedded database is usually better on the instance’s own volume with backups.
Instance replacement
Deploys, app crashes and restarts, and routine in-place stack updates leave instances and their disks in place. The following events replace an instance:
The default
EC2 health check reflects the AWS instance and system status checks: the instance is unreachable, its network or boot state is broken, or the underlying host has failed. It says nothing about your application. A crashed app process does not fail it, because supervisord restarts the process on the same instance, and a failing HTTP health check does not fail it either.
In practice that makes health-driven replacement an infrequent event tied to underlying hardware or hypervisor failure, not something a bad release or a wedged app triggers. The tradeoff is that a standalone EC2 instance you launch in the console would instead sit impaired until you noticed, while the Auto Scaling Group recovers automatically with a fresh instance and an empty disk.
Load-balancer health checks do not replace instances. health_check_type is EC2 by default on purpose, because in-place deploys briefly deregister an instance from the target group and ELB health checks would read that as unhealthy and replace it mid-deploy. Set health_check_type to ELB through Advanced Terraform variables only if you accept that, and expect replacement — and disk loss — whenever the ALB considers an instance unhealthy.
Scaling and instance health
Minimum instances and Maximum instances bound the group. Enable CPU autoscaling to target average EC2 CPU utilization. When autoscaling is off, Desired instances sets the initial group size; later external scaling changes are preserved. Instance health check grace period controls how long new instances have before Auto Scaling begins health evaluation. Launch template changes such as a new AMI, instance type, volume setting, or Additional user data apply to newly launched instances only. The module does not perform an instance refresh, so an in-place stack update does not roll a new AMI onto running instances; recycle them deliberately when a host-level change must go out. Recycling replaces the instance, which deletes its root and data volumes, so restore from a snapshot or backup afterwards if the instance held state you need.Application configuration
Runtime environment variables must be single-line values. They are rendered into the app environment file at instance boot and on every deploy. Container mode passes the file to Docker; Manual mode loads it before Deploy commands and the Start command. A configuration change requires a stack update and then a deploy to restart the app with the new values. Runtime secrets use same-account Secrets Manager or SSM Parameter Store references. Each instance fetches values while rebuilding its environment file, so the values do not enter Terraform state or the SSM document. A rotated value takes effect on the next deploy without a stack change. Multi-line secret values are unsupported. Build environment variables apply to Railpack and Dockerfile builds; for Dockerfile builds, optionally inject them as build arguments.Configuration
Service and instances
Container builds
Git branch and Git ref are optional inputs on each build run. They default to the repository’s default branch and its current head.
Web routing and health
A web service can use a standalone Application Load Balancer or the public or private ALB from an ECS cluster. The selected load balancer must belong to the same AWS account, region, and VPC as the selected VPC network. When both HTTPS and HTTP listeners exist, the service attaches its listener rule to HTTPS.Application data and operations
*Conditionally required or visible based on Deploy type, Build source, Web service, Load balancer source, CPU autoscaling, Data volume, or EFS file system.
Design decisions
- ECS is the default recommendation for stateless container services. Use this module when manual host deployment, host customization, or stable-host behavior is an actual requirement.
- Deploys are in place by design. They do not replace instances, so instance disks are durable for the life of the instance; scaling, health recovery, and deliberate recycling for a launch template change are what replace an instance.
- Instances are prepared for both deploy modes so the Deploy type can change without replacing the group.
- Supervisord manages one app process per instance. A one-instance service is interrupted while that process is replaced.
- Supervisor 4.3.0 is installed from PyPI. Instances need outbound access to PyPI or an equivalent package source during bootstrap and when deploys ensure the pinned version.
- One app per instance group. Sharing instances between apps is intentionally out of scope; share the load balancer instead.
- Load balancing stays shared: use either a standalone ALB module or a public/private ALB from an ECS cluster while this service owns its target group and listener rule.
- The ASG health check type defaults to EC2 because in-place deploys briefly deregister instances from the target group.
- Launch template changes do not trigger an instance refresh, preserving per-host state until you choose to recycle instances.
- Worker groups omit load-balancer metrics instead of rendering charts with empty target-group dimensions.
Learn more
- Amazon EC2 Auto Scaling
- AWS Systems Manager Run Command
- Application Load Balancer listener rules
- Amazon ECS services
- Supervisor documentation
- Railpack documentation
Inputs reference
All inputs forrvn-ec2-service version 2.5.0. Use the name shown for each field as the input key in module config.
- Inputs: Inputs to logging — Inputs, EC2 service, Build config, Dockerfile, Railpack, Deployment, HTTP routing, HTTP listener rules, Health check, Storage, Scaling, Environment variables, Networking, Builder config, Container registry, Logging
- Inputs: Terraform settings to terraform settings — Terraform settings