How to host Unleash on AWS: six ways to run self-hosted feature flags
Unleash is open source, and every part of it can run inside your own AWS account. You get full control over where your data lives, which networks the service can reach, and when you upgrade. This guide walks through the main compute services AWS offers and shows how Unleash fits on each one, from a single container for a quick evaluation up to a multi-region Kubernetes setup.
We run Unleash Cloud on AWS ourselves, with the API and Admin UI on Kubernetes talking to Amazon RDS, so a lot of what follows mirrors what we do in production. If you would rather not operate any of it, host it on Unleash Cloud or check the AWS Marketplace listing. This guide is for the case where you want it running in your own infrastructure.
What an Unleash deployment actually looks like
Before picking a compute service, it helps to know how little Unleash needs. There are three parts, and only one of them holds state.
The Unleash server is a stateless Node.js application. You run it from the unleashorg/unleash-server image (or unleashorg/unleash-enterprise if you have an Enterprise license), it listens on port 4242, and it serves both the REST API and the Admin UI. Because it keeps no state of its own, you can run one copy or twenty behind a load balancer and they all behave the same way.
PostgreSQL is the one stateful piece. Unleash stores flags, projects, environments, users, and audit history there, and you need version 15 or newer. On AWS this almost always means Amazon RDS for PostgreSQL or Aurora PostgreSQL, and your choice of compute service for the server does not change that.
Unleash Enterprise Edge is optional. It is a lightweight, read-only cache that sits between your SDKs and the server. It runs from the unleashorg/unleash-edge-enterprise image on port 3063, and it exists to absorb very high evaluation traffic and cut latency. A single Edge node handles tens of thousands of requests per second, so most teams add it only once they need global reach or serious throughput. You can deploy Edge to any machine, anywhere in the world, and with streaming enabled all the changes will instantly propagate from Unleash to all the Edge instances across the globe.
The job in every section below is the same. Run the stateless server container, point it at Postgres, put it behind HTTPS, and keep your secrets out of plain text. The compute service is the part that changes. Everything else is shared, so let’s cover the shared part once.
The foundation every option shares
The database
Create an RDS for PostgreSQL instance (15 or newer) in private subnets, and enable Multi-AZ if this is production so a failover completes in about two minutes. Give it a security group that only accepts connections on port 5432 from the security group your Unleash server runs in. Nothing else should be able to reach it.
Unleash reads its connection details from environment variables. You can pass a single DATABASE_URL:
DATABASE_URL=postgres://unleash_user:PASSWORD@your-db.abc123.eu-west-1.rds.amazonaws.com:5432/unleash?ssl=true
or set DATABASE_HOST, DATABASE_PORT, DATABASE_NAME, DATABASE_USERNAME, and DATABASE_PASSWORD separately.
RDS expects TLS, so you need to tell Unleash to use it. Download AWS’s global RDS certificate bundle (the global-bundle.pem file published in the RDS SSL/TLS documentation), make it available inside the container, and set:
DATABASE_SSL_CA_FILE=/etc/unleash/rds-global-bundle.pem
That gives you a verified TLS connection. If you are just kicking the tires in a throwaway environment and do not want to mount a certificate yet, DATABASE_SSL_REJECT_UNAUTHORIZED=false will connect without verifying the chain. And you’ll need DATABASE_SSL=true as well. Of course, keep that out of production.
For the database engine, plain RDS for PostgreSQL is the predictable default. You pick an instance size and pay for it around the clock. Aurora Serverless v2 is worth a look if your load is spiky, since it scales capacity automatically, though it usually costs more at a steady baseline. You are not limited to those two. For a small or non-critical setup you can run Postgres in a container on the same box as the server, or use a Lightsail managed database if you are staying inside Lightsail. Third-party managed Postgres services work just as well, and several of them run on AWS under the hood, so you can keep the data in the same region as your compute:
- Aiven for PostgreSQL runs on AWS, GCP, and Azure with a 99.99% uptime SLA, automatic failover, and point-in-time recovery.
- Neon is serverless Postgres that separates storage from compute and can scale to zero, which is handy for development and spiky workloads.
- PlanetScale for Postgres runs real Postgres on their high-performance infrastructure and defaults to AWS.
- Supabase runs your Postgres database in the AWS region you choose, alongside the rest of its platform.
Unleash only ever sees a Postgres endpoint, so as long as it speaks PostgreSQL 15 or newer, the connection string is the only thing that changes.
Secrets
Do not bake the database password or admin credentials into a task definition or a plain environment variable. Store them in AWS Secrets Manager or SSM Parameter Store and inject them at runtime. Every compute service below has a native way to do this, and the pattern is always the same: the secret lives in Secrets Manager, and the platform hands it to the container as an environment variable at startup. The examples show where that plugs in.
HTTPS and the public URL
Terminate TLS at an Elastic Load Balancer (ALB or NLB) with a certificate from AWS Certificate Manager, and forward plain HTTP to the container on 4242. Point the load balancer’s health check at GET /health, which Unleash exposes for exactly this purpose.
Two settings matter once you are behind HTTPS. Set UNLEASH_URL to the public address (for example https://unleash.yourcompany.com) so that generated links and invitations are correct, and set SECURE_HEADERS=true to turn on HSTS and related headers. Change the default admin password with UNLEASH_DEFAULT_ADMIN_PASSWORD, or wire up SSO and disable the built-in admin account entirely.
Edge, when you need it
If you add Unleash Enterprise Edge later, it follows the same rules as the server. It is a stateless container, so you run two or more instances behind a load balancer, configured with UPSTREAM_URL pointing at your Unleash server (without the /api suffix) and a TOKENS value to bootstrap its cache. Give it a persistence layer through REDIS_URL, S3_BUCKET_NAME, or a BACKUP_FOLDER so that a restart does not cold-start against your server, and raise the file handle limit (–ulimit nofile=65536:65536) for heavy traffic. Everything about placing the server on a given AWS service applies to Edge too, so we will not repeat it. Read more about self-hosting Edge on our docs.
With that in place, here are the six ways to run the server, ordered from the least operational work to the most control.
1. AWS Lightsail: the simplest place to start
Lightsail is AWS with most of the dials removed. It gives you container hosting and managed databases at a flat monthly price, which makes it a great fit for a proof of concept, an internal tool, or a small team that wants Unleash running today without learning VPC networking first.
Create a Lightsail container service, then push a deployment that runs the Unleash image, exposes port 4242, and uses /health as its health check path. Lightsail also offers a managed PostgreSQL database you can create in a couple of clicks, so you can keep the whole thing inside Lightsail, or point DATABASE_URL at an RDS instance instead.
A minimal deployment definition looks like this:
{
"containers": {
"unleash": {
"image": "unleashorg/unleash-server:latest",
"ports": { "4242": "HTTP" },
"environment": {
"DATABASE_URL": "postgres://unleash_user:PASSWORD@host:5432/unleash",
"DATABASE_SSL_REJECT_UNAUTHORIZED": "false"
}
}
},
"publicEndpoint": {
"containerName": "unleash",
"containerPort": 4242,
"healthCheck": { "path": "/health" }
}
}
Create the service once, then push that spec as a deployment with the AWS CLI:
aws lightsail create-container-service \
--service-name unleash --power small --scale 1
aws lightsail create-container-service-deployment \
--service-name unleash --cli-input-json file://deployment.json
Lightsail gives you an HTTPS endpoint on an amazonlightsail.com subdomain out of the box, so you do not even need a load balancer to get TLS. The trade-off is the ceiling. You get fixed container sizes and simple scaling, so once you outgrow it you will graduate to one of the options below.
For getting started, nothing is faster than Lightsail.
2. Amazon ECS Express Mode: managed containers without the plumbing
If you were about to reach for AWS App Runner, this is where you land now. AWS closed App Runner to new customers in April 2026 and pointed people to ECS Express Mode instead. Hand it a container image and you get back an autoscaling HTTPS service, with no clusters, listeners, or scaling policies to write.
You provide a container image and two IAM roles, and ECS Express Mode provisions the whole stack in your own account: a Fargate service, an Application Load Balancer with SSL/TLS, auto scaling, networking, and a public URL. There is no extra charge for Express Mode itself, so you pay only for the Fargate tasks, the load balancer, and the usual CloudWatch and data transfer costs.
Point it at the Unleash image, set the container port to 4242, and use /health as the health check path. Keep the database password in Secrets Manager and grant the task execution role access to it, then pass the rest of the configuration as environment variables. Because the tasks run on Fargate inside your account, they can reach an RDS instance in the same VPC directly, so you allow the service’s security group on port 5432, with no separate VPC connector.
A single call creates everything:
aws ecs create-express-gateway-service \
--service-name unleash \
--execution-role-arn arn:aws:iam:::role/ecsTaskExecutionRole \
--infrastructure-role-arn arn:aws:iam:::role/ecsInfrastructureRoleForExpressServices \
--primary-container '{
"image": "public.ecr.aws/unleashorg/unleash-server:latest",
"containerPort": 4242,
"environment": [
{ "name": "DATABASE_HOST", "value": "your-db.abc123.eu-west-1.rds.amazonaws.com" },
{ "name": "DATABASE_NAME", "value": "unleash" },
{ "name": "DATABASE_USERNAME", "value": "unleash_user" },
{ "name": "UNLEASH_URL", "value": "https://unleash.yourcompany.com" },
{ "name": "SECURE_HEADERS", "value": "true" }
]
}' \
--health-check-path "/health" \
--scaling-target '{"minTaskCount":2,"maxTaskCount":6}' \
--monitor-resources
Provisioning takes a few minutes, after which you get a URL to test against. When you outgrow the defaults, nothing is hidden. Everything Express Mode created is a standard ECS service, load balancer, and set of scaling policies that you can manage directly, which is exactly the setup in the ECS on Fargate section below. That makes it a low-risk starting point, since growing into full ECS later is a change of tooling rather than a migration.
3. AWS Elastic Beanstalk: a familiar PaaS with more knobs
If your team already thinks in terms of Elastic Beanstalk environments, Unleash drops in cleanly as a Docker application. Beanstalk provisions the EC2 instances, the Application Load Balancer, and the Auto Scaling group for you, while still letting you reach in and adjust them when you need to.
Describe the container with a Dockerrun.aws.json that runs the Unleash image and maps port 4242, set your environment properties (including the DATABASE_* values) in the environment configuration, and let Beanstalk stand up the load balancer. Attach the ACM certificate to the load balancer’s listener for HTTPS, and configure the health check to hit /health.
A single-container Dockerrun.aws.json is all Beanstalk needs to know which image to run:
{
"AWSEBDockerrunVersion": "1",
"Image": {
"Name": "public.ecr.aws/unleashorg/unleash-server:latest",
"Update": "true"
},
"Ports": [{ "ContainerPort": 4242 }]
}
Set the connection details and other variables with eb setenv DATABASE_HOST=… DATABASE_NAME=unleash SECURE_HEADERS=true, or from the environment’s configuration page, and keep the password in Secrets Manager.
You can have Beanstalk create an RDS instance as part of the environment, but we’d suggest to keep the database separate from the application environment, so that tearing down or recreating the app never puts the data at risk.
Beanstalk is a comfortable middle ground, with more control than ECS Express Mode and less assembly than wiring up ECS by hand.
4. Amazon ECS on Fargate: the production default for most teams
This is where most teams land. ECS on Fargate runs your containers with no servers to patch, gives you fine-grained control over CPU, memory, networking, IAM, and autoscaling, and integrates with the rest of your AWS account. It is also the setup we would recommend for a serious self-hosted deployment at scale.
You define a task definition that describes the Unleash container, then run it as a service behind an Application Load Balancer, with two tasks spread across Availability Zones. Here is a task definition with the database password pulled from Secrets Manager and logs shipped to CloudWatch:
{
"family": "unleash",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam:::role/unleashTaskExecutionRole",
"containerDefinitions": [
{
"name": "unleash",
"image": "unleashorg/unleash-server:latest",
"portMappings": [{ "containerPort": 4242, "protocol": "tcp" }],
"environment": [
{ "name": "DATABASE_HOST", "value": "your-db.abc123.eu-west-1.rds.amazonaws.com" },
{ "name": "DATABASE_NAME", "value": "unleash" },
{ "name": "DATABASE_USERNAME", "value": "unleash_user" },
{ "name": "DATABASE_SSL_REJECT_UNAUTHORIZED", "value": "false" },
{ "name": "UNLEASH_URL", "value": "https://unleash.yourcompany.com" },
{ "name": "SECURE_HEADERS", "value": "true" }
],
"secrets": [
{
"name": "DATABASE_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:eu-west-1::secret:unleash/db-password"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/unleash",
"awslogs-region": "eu-west-1",
"awslogs-stream-prefix": "unleash"
}
}
}
]
}
Create an ECS service from that task with a desired count of two, register it with an ALB target group whose health check path is /health, and put the target group behind an HTTPS listener that uses your ACM certificate. Set the RDS security group to accept 5432 from the service’s security group and nothing else. For scaling, attach a target-tracking policy on CPU utilization so the service adds tasks under load and removes them when things are quiet.
From the CLI, that is a register call followed by a service call:
aws ecs register-task-definition --cli-input-json file://unleash-task.json
aws ecs create-service \
--cluster unleash \
--service-name unleash \
--task-definition unleash \
--desired-count 2 \
--launch-type FARGATE \
--load-balancers targetGroupArn=,containerName=unleash,containerPort=4242 \
--network-configuration "awsvpcConfiguration={subnets=[subnet-a,subnet-b],securityGroups=[sg-unleash]}"
The task definition parameters reference documents every field you can set in the definition above.
The result is a stateless service that survives an Availability Zone going down, deploys with zero downtime because ECS drains old tasks before it removes them, and costs you nothing for idle servers because there are none. For a production reference, swap the relaxed SSL setting above for the mounted certificate approach and set DATABASE_SSL_CA_FILE.
5. Amazon EC2: full control and docker-compose
Sometimes you want a plain server you can SSH into. EC2 gives you exactly that, and it is the natural home for the docker-compose setup from our getting-started guide, for air-gapped environments, and for anyone who prefers to own the whole stack.
Launch an instance, install Docker, and run the Unleash container against your RDS database. You can use the same docker run you would use anywhere:
docker run -d -p 4242:4242 \
-e DATABASE_URL=postgres://unleash_user:PASSWORD@your-db.abc123.eu-west-1.rds.amazonaws.com:5432/unleash \
-e DATABASE_SSL_CA_FILE=/etc/unleash/rds-global-bundle.pem \
-e UNLEASH_URL=https://unleash.yourcompany.com \
-e SECURE_HEADERS=true \
-v /etc/unleash:/etc/unleash:ro \
--name unleash \
unleashorg/unleash-server
On a box you intend to keep running, a small docker-compose.yml is easier to manage than a raw docker run, and it makes the certificate mount and the restart policy explicit:
services:
unleash:
image: unleashorg/unleash-server:latest
ports:
- "4242:4242"
environment:
DATABASE_URL: postgres://unleash_user:PASSWORD@your-db.abc123.eu-west-1.rds.amazonaws.com:5432/unleash
DATABASE_SSL_CA_FILE: /etc/unleash/rds-global-bundle.pem
UNLEASH_URL: https://unleash.yourcompany.com
SECURE_HEADERS: "true"
volumes:
- /etc/unleash:/etc/unleash:ro
restart: unless-stopped
For a quick evaluation you can even run Postgres in a second container on the same instance. And for anything you care about, point it at RDS so backups and failover are not your job.
For HTTPS you have two choices. Put the instance behind an Application Load Balancer with an ACM certificate, which is the cleaner option and the one that lets you add more instances later, or terminate TLS on the box itself with something like Caddy or nginx. Run at least two instances across Availability Zones behind the load balancer if this is production, because a single EC2 instance is a single point of failure.
The cost of all this control is that patching the OS, renewing certificates if you terminate locally, and scaling are now your responsibility. EC2 is the right call when you have specific requirements the managed services cannot meet. It is more work than you need if you do not.
6. Amazon EKS: Kubernetes for scale and standardization
If you already run Kubernetes, or you are planning for multi-region and heavy scale, EKS fits the way large platform teams already work. It is also closest to how we run Unleash Cloud, where the API and UI live in a Kubernetes deployment in front of RDS.
We publish an official Helm chart, so you do not have to hand-write manifests:
helm repo add unleash https://docs.getunleash.io/helm-charts
helm repo update
helm install unleash unleash/unleash -f my-values.yaml
A minimal my-values.yaml sets the replica count, points at RDS, pulls the password from a secret you created, and asks the load balancer controller for an ALB:
replicaCount: 2
dbConfig:
host: your-db.abc123.eu-west-1.rds.amazonaws.com
port: 5432
database: unleash
user: unleash_user
useExistingSecret:
name: unleash-db
key: password
ingress:
enabled: true
className: alb
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:eu-west-1::certificate/
hosts:
- host: unleash.yourcompany.com
paths:
- path: /
pathType: Prefix
The chart’s values.yaml documents every option. Expose the deployment through an Ingress backed by the AWS Load Balancer Controller, which provisions the ALB and terminates TLS with your ACM certificate, and add a HorizontalPodAutoscaler so the number of pods tracks CPU (or even custom metrics that the Unleash server exposes such as request latency or Node.js event loop lag). Because the server is stateless, scaling is just a matter of raising the replica count.
For a full walkthrough with values files and the Ingress setup, we have a dedicated post on self-hosting Unleash on Kubernetes with Helm.
EKS carries the most operational overhead of the six, and in return it gives you the scheduling, self-healing, and standardization that make it worth the effort once you are operating at large scale.
A note on AWS Lambda
You might wonder where serverless functions fit. For the Unleash server, they aren’t a good fit because the server is a long-lived process. It holds a pool of database connections, runs background jobs for metrics and maintenance, and serves the Admin UI. Lambda’s short-lived, connection-per-invocation model works against all of that, so we suggest running the server on one of the container or VM options above.
Your applications are a different story. A backend SDK evaluates flags locally, in memory, inside your own process, so an application running on Lambda can use Unleash perfectly well. It fetches flag configuration from the server or from Edge and evaluates in place. Lambda is a fine home for the code that consumes flags. It is just not the place for the server that manages them.
How to choose
In practice, the database is always RDS or Aurora and the real decision is how much of the compute you want to operate yourself.
If you want:
- The fastest start, or a small internal setup, use Lightsail (lower effort).
- Production traffic with almost no ops, use ECS Express Mode (very low effort).
- A familiar PaaS you can still tune, use Elastic Beanstalk (low to medium effort).
- A solid production default, use ECS on Fargate (medium effort).
- Full control, docker-compose, or air-gapped, use EC2 (high effort).
- Kubernetes, multi-region, large scale, use EKS (highest effort).
If you are not sure, start with ECS Express Mode or ECS on Fargate. They cover the vast majority of production needs, they scale, and do not ask you to manage servers. You can always move later, because the server is stateless and your data stays in RDS the whole time.
Where to go next
Whichever option you pick, the pattern holds. You run a stateless server, back it with a Postgres database, put HTTPS in front, keep secrets in Secrets Manager, and add Unleash Enterprise Edge when your evaluation traffic calls for it. Start with the self-hosting getting-started guide for the base configuration, read scaling Unleash when you are planning for high availability, and use the Terraform provider to manage projects, environments, and long-lived flags as code once you are up and running.
And if at any point you would rather hand the operations back to us, Unleash Cloud and hybrid hosting run all of this on AWS for you, so you can keep Unleash and Unleash Edge close to your apps and let us look after the server and the database.



