Skip to main content

Install on AWS

This guide will perform a complete production-ready installation in your AWS environment using AWS CDK.

The resulting AWS configuration should look like the following:

Medplum AWS Architecture

Prerequisites

You will need permission to access the following AWS services:

ServiceDetails
Elastic Compute Cloud (EC2)Create a Virtual Private Cloud (VPC) and related security groups
ElasticacheCreate a hosted Redis cluster for caching and task queue
Elastic Load Balancing (ELB)Create a load balancer for server redundancy and high availability
Identity and Access Management (IAM)Create service roles for the API server and bot lambdas
CloudFrontSecurely deliver content with low latency and high transfer speeds
CloudWatch LogsCreate and manage log groups for server logs
Relational Database Service (RDS)Create a hosted Postgres Aurora database
Route 53Create DNS entries for the services
Simple Storage Service (S3)Host static web content, store and retrieve dynamic user content for file attachments
Secrets ManagerStore encrypted secret configuration details such as database credentials
Systems Manager (SSM)Store configuration details
Web Application Firewall (WAF)Protect your web applications or APIs against common web exploits and bots

Setup

Most AWS resources are automatically created using CDK, but some either cannot or are not recommended. Please follow these initial setup steps to prepare your account.

Create HTTPS certificates

You will need the ARN for ACM Certificates for the following hosts:

  1. App (i.e., app.example.com)
  2. API (i.e., api.example.com)
  3. Storage (i.e., storage.example.com)

Follow the Requesting a public certificate guide three times, one for each certificate.

While CDK can provision certificates, it is not recommended.

Create a signing key

Why do I need a signing key?

Medplum uses S3 and CloudFront for binary content such as images, videos, and other file attachments. To serve this content, the API server generates AWS CloudFront signed URLs which are valid for a configurable period of time.

AWS CloudFront signed URLs have many desirable properties. They are fast and virtually infinitely scalable. They work well with standard web techniques such as `<img>` and `<video>` tags. They support HTTP Range Requests for optimal video performance.

Generate a 2048 bit RSA key:

openssl genrsa -des3 -out private.pem 2048

Export the public key to a file:

openssl rsa -in private.pem -outform PEM -pubout -out public.pem

For more details, see AWS CloudFront - Using signed URLs.

Add the signing key to CloudFront

Follow the Creating key pairs for your signers guide to add your signing key to CloudFront.

Make note of the key ID for future steps.

Create a S3 bucket for logging

Some components of the stack can be configured to capture detailed information, stored in an S3 bucket. This is optional, and disabled by default.

If you want to capture these detailed logs, follow these steps:

First, follow the Creating a bucket guide to create an S3 bucket for logging. All log backups will be stored in this directory.

Second, follow the Access logs for your Application Load Balancer guide to setup permissions to the bucket.

Make note of the bucket name for the following CDK config settings below:

  • loadBalancerLoggingEnabled
  • loadBalancerLoggingBucket
  • loadBalancerLoggingPrefix
  • clamscanEnabled
  • clamscanLoggingBucket
  • clamscanLoggingPrefix

Create a SES email address

Follow the Creating and verifying identities in Amazon SES guide to register an email address for system generated emails.

Create a CDK config file

Create a Medplum CDK config file. This is a JSON file that contains all of the custom infrastructure configuration settings of the new environment. Note that this is distinct from the server config file (see next section).

Here is a full example. See the table below for details on each setting.

KeyDescription
nameThe short name of your environment. This should be unique among your Medplum deployments. This will be used as part of Parameter Store path and CloudWatch Logs path. For example, prod or staging.
stackNameThe long name of your environment. This will be included in many of the AWS resource names created by CDK. For example, MyMedplumStack or MedplumStagingStack.
accountNumberYour AWS account number. A 12-digit number, such as 123456789012, that uniquely identifies an AWS account. Account IDs are not considered sensitive information.
regionThe AWS region where you want to deploy. Note: Only use-east-1 is supported at this time.
domainNameThe domain name that represents your Route 53 Hosted Zone. For example, medplum.com or my-med-app.io.
apiDomainNameThe domain name of the API server. This should be a subdomain of domainName. For example, api.medplum.com or api.staging.medplum.com.
apiPortThe port number that the API server binds to inside the Docker image. By default, you should use 8103. In some cases, you may need to use 5000.
apiSslCertArnThe ARN of the ACM Certificate for the API server domain that you registered before.
appDomainNameThe domain name of the app server. This should be a subdomain of domainName. For example, app.medplum.com or app.staging.medplum.com.
appSslCertArnThe ARN of the ACM Certificate for the app server domain that you registered before.
storageBucketNameThe name of the S3 bucket for file storage that you created before.
storageDomainNameThe domain name that will be used to access the file storage using presigned URLs. For example, storage.medplum.com.
storageSslCertArnThe ARN of the ACM Certificate for the storage server domain that you registered before.
storagePublicKeyThe contents of the public key file that you created before. By default, the file name is public.pem. The contents should start with -----BEGIN PUBLIC KEY-----.
maxAzsThe maximum number of availability zones to use. If you want to use all availability zones, choose a large number such as 99. If you want to restrict the number, for example to manage EIP limits, then choose a small number such as 1 or 2.
rdsInstancesThe number of running RDS instances. Use 1 for a single instance, or 2 for a hot failover on standby.
desiredServerCountThe number of running ECS/Fargate instances in steady state. Use 1 when getting started, and increase as necessary or for high availability.
serverImageThe DockerHub server image to deploy. Use medplum/medplum-server:latest for the most recent version published by the Medplum team. Or use your own repository if you need to deploy a custom instance.
serverMemoryThe amount (in MiB) of memory used by the ECS/Fargate instance. For example, 512, 1024, 2048, 4096, etc. See Task size.
serverCpuThe number of cpu units used by the task. For example, 512, 1024, 2048, 4096, etc. See Task size.
loadBalancerLoggingEnabledBoolean flag to Enable Access Logs to ELB
loadBalancerLoggingBucketThe logging bucket that you created before.
loadBalancerLoggingPrefixA directory prefix to use for the S3 logs. For example, elb.
clamscanEnabledBoolean flag to enable Serverless ClamScan antivirus
clamscanLoggingBucketThe logging bucket that you created before.
clamscanLoggingPrefixA directory prefix to use for the S3 logs. For example, clamscan.

Here is the server configuration for the Medplum staging environment:

{
"name": "staging",
"stackName": "MedplumStagingStack",
"accountNumber": "647991932601",
"region": "us-east-1",
"domainName": "medplum.com",
"apiDomainName": "api.staging.medplum.com",
"apiPort": 5000,
"apiSslCertArn": "arn:aws:acm:us-east-1:647991932601:certificate/159b257b-a180-49c6-b188-4dc962d8e708",
"appDomainName": "app.staging.medplum.com",
"appSslCertArn": "arn:aws:acm:us-east-1:647991932601:certificate/b0d65b27-2ea8-4377-82e1-c41aa067655b",
"storageBucketName": "medplum-staging-storage",
"storageDomainName": "storage.staging.medplum.com",
"storageSslCertArn": "arn:aws:acm:us-east-1:647991932601:certificate/2205bb8c-7da9-4992-b8ec-c2c79b43b586",
"storagePublicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3cnmD3HQbJU7WTGT2ZSO\nLt71c+xQ91m5FAzdFagfkQAG0CeyzHq8VzjLPinLDlOWCwQXfunjoBMP7iyVt/pE\n46ngR55In3UlzsMySHpUAi740u6oh0VeJOZA1x/FrVRYsxFx4XFJ92gcs5VvdT66\nwWTX7KznaIrxIvTWz384ogqXfg41QeoIISM2YUjqSMkyx7wY3xGrFvG5UuAAivbr\ni/ZZkkM2q9frpidpJx4evIuaHZS8fstbHFDbbFFqDMyuk7eAJRea1KH5TsjCHvTK\n5ANRyzq+mty47TKrI+2AQsxjH4mel2lC/at3udgtmfz1MTT7daFWfDKsVn8h3DsA\nJwIDAQAB\n-----END PUBLIC KEY-----",
"maxAzs": 2,
"rdsInstances": 1,
"desiredServerCount": 1,
"serverImage": "medplum/medplum-server:latest",
"serverMemory": 512,
"serverCpu": 256,
"loadBalancerLoggingEnabled": true,
"loadBalancerLoggingBucket": "medplum-logs-us-east-1",
"loadBalancerLoggingPrefix": "elb-staging",
"clamscanEnabled": true,
"clamscanLoggingBucket": "medplum-logs-us-east-1",
"clamscanLoggingPrefix": "clamscan"
}

Make note of this file name.

Create a server config

Create a Medplum server config in AWS Parameter Store.

TODO: Write documentation for how to use JSON config file format with Docker and CDK. It is technically possible using either a custom Dockerfile or a Docker layer on top of the official Medplum Docker image. It would require modifying the MedplumTaskDefinition command in backend.ts.

When running Medplum server on a local developer machine, Medplum server typically loads config settings from a JSON config file. By default, it loads config settings from medplum.config.json.

When running in AWS, Medplum server loads config settings from AWS Parameter Store, a feature of AWS Systems Manager (SSM).

Some configuration settings are created automatically by the CDK deployment (for example, database and redis connection details). Other settings must be created manually before the first deploy.

When creating a parameter in AWS Parameter Store, you will be prompted for the parameter Name. The parameter Name uses the convention /medplum/{environmentName}/{key}.

For example, if your environment name is "prod", then the "baseUrl" parameter name is /medplum/prod/baseUrl.

KeyDescriptionAutomatic
portThe port number that the API server binds to inside the Docker image. By default, you should use 8103. In some cases, you may need to use 5000.no
baseUrlThe fully qualified base URL of the API server including a trailing slash. For example, https://api.example.com/.no
issuerThe JWK issuer. By default, Medplum server uses built in OAuth, so issuer should be the same as baseUrl.no
audienceThe JWK audience. By default, Medplum server uses built in OAuth, so audience should be the same as baseUrl.no
jwksUrlThe JWKS URL. By default, Medplum server uses built in OAuth, so jwksUrl should be baseUrl + .well-known/jwks.json.no
authorizeUrlThe OAuth authorize URL. By default, Medplum server uses built in OAuth, so authorizeUrl should be baseUrl + oauth2/authorize.no
tokenUrlThe OAuth token URL. By default, Medplum server uses built in OAuth, so tokenUrl should be baseUrl + oauth2/token.no
userInfoUrlThe OAuth userinfo URL. By default, Medplum server uses built in OAuth, so userInfoUrl should be baseUrl + oauth2/userinfo.no
appBaseUrlThe fully qualified URL of the user-facing app. This is used for CORS and system generated emails. For example, https://app.example.com/.no
binaryStorageWhere to store binary contents. This should be the CDK config storageBucketName with s3: prefix. For example, s3:medplum-storage.no
storageBaseUrlThe fully qualified base URL of the binary storage. This should be the CDK config storageDomainName with https:// prefix. For example, https://storage.medplum.com/binary/.no
signingKeyIdThe AWS key ID of the CloudFront signing key that you created before.no
signingKeyThe private key of the CloudFront signing key.no
signingKeyPassphraseThe passphrase of the CloudFront signing key.no
supportEmailThe email address to use when sending system generated messages. This email address must be registered in AWS SES.no
googleClientIdIf using Google Authentication, this is the Google Client ID.no
googleClientSecretIf using Google Authentication, this is the Google Client Secret.no
recaptchaSiteKeyIf using reCAPTCHA, this is the reCAPTCHA site key.no
recaptchaSecretKeyIf using reCAPTCHA, this is the reCAPTCHA secret key.no
botLambdaRoleArnyes
botLambdaLayerNameno
databaseThe database connection details (created automatically).yes
redisThe redis connection details (created automatically).yes

TODO: Update the server with intelligent defaults to reduce the number of implicit configuration settings.

TODO: Investigate creating a signing key automatically on first run.

Synth

Run CDK synth:

npx cdk synth -c config=my-config.json

Deploy

Run CDK deploy:

npx cdk deploy -c config=my-config.json

Diff

Run CDK diff:

npx cdk diff -c config=my-config.json