-
Notifications
You must be signed in to change notification settings - Fork 7
Crystal on AWS
See Also: This article describes how to use a remote version of Crystal to open a remote project on AWS S3. If you instead want to run a local version of Crystal that opens a project on AWS S3, see Projects on AWS S3 instead.
Crystal can serve an archived website directly from AWS, making it accessible to anyone with a web browser; no need to install Crystal locally. This uses AWS Lambda to run Crystal and Amazon S3 to store the project.
-
An AWS account. Create one if you don't have one already.
-
A
.crystalprojuploaded to S3. Create an S3 bucket and upload your project directory to it. See Projects on AWS S3 for details on uploading, including the recommended Pack16 format conversion.
Tip: After uploading, copy the S3 URL of your
.crystalprojfrom the S3 Console; you'll need it in the next step. It looks like:s3://my-bucket/My Site.crystalproj
Click one of the buttons below to open the AWS CloudFormation Console with the Crystal template pre-loaded:
| Stable (latest release) | ![]() |
| Dev (main branch, may be unstable) | ![]() |
Then:
-
Stack name: Choose a name for this deployment (e.g.
crystal-my-site). Each archived site you deploy needs its own stack name. -
Project S3 URL: Paste the S3 URL of your
.crystalproj(e.g.s3://my-bucket/My Site.crystalproj). You can copy this directly from the S3 Console.
-
(Optional) Under Password Protection, enter a Username and Password to restrict access to the site. Leave both blank for public access.
-
Leave the Advanced Options at their defaults.
-
Scroll down and check the box "I acknowledge that AWS CloudFormation might create IAM resources with custom names." This is required because the template creates an IAM role that allows the Lambda function to read your project from S3.
-
Click Create stack.
-
Wait for the stack status to reach CREATE_COMPLETE (typically 2-4 minutes).
- Go to the Outputs tab. The SiteUrl1 value is the public URL where your archived site is now accessible.
If you prefer to inspect the template before deploying, or if the Quick Deploy links above don't work for your region:
-
Download the CloudFormation template:
crystal.cloudformation.yaml -
Open the AWS CloudFormation Console.
-
Click Create stack > With new resources (standard).
-
Select Upload a template file, choose the downloaded
crystal.cloudformation.yaml, and click Next. -
Follow the same steps as Quick Deploy above (fill in stack name, Project S3 URL, acknowledge IAM, create).
By default, your site is served at an auto-generated URL like https://abc123.lambda-url.us-east-2.on.aws/. To use your own domain (e.g. mysite.example.com), you can add a custom domain using AWS CloudShell, a browser-based terminal built into the AWS Console that requires no local setup.
Prerequisites: A domain you own, with access to manage its DNS records at your registrar.
-
Open AWS CloudShell.
-
In the top-right corner of the page, select the region matching the one your Crystal stack is deployed in (e.g. "US East (Ohio) us-east-2").
-
Paste and run this command, replacing
<stack-name>and<domain>with your values:curl -sL https://raw.githubusercontent.com/davidfstr/Crystal-Web-Archiver/main/src/crystal_on_aws/custom-domain-add.sh | bash -s <stack-name> <domain>For example:
curl -sL https://raw.githubusercontent.com/davidfstr/Crystal-Web-Archiver/main/src/crystal_on_aws/custom-domain-add.sh | bash -s crystal-my-site mysite.example.com -
The script will display two CNAME records to add at your domain registrar. Add both records, then press Enter in CloudShell to continue.
- Wait for the script to finish (typically 5-10 minutes). When complete, your site will be live at
https://mysite.example.com/.
-
Open AWS CloudShell and select the correct region.
-
Paste and run:
curl -sL https://raw.githubusercontent.com/davidfstr/Crystal-Web-Archiver/main/src/crystal_on_aws/custom-domain-remove.sh | bash -s <stack-name> -
The script will list the resources to be deleted and ask for confirmation. After completion, it will remind you to remove the CNAME records at your registrar.
You can restrict access to your archived site by requiring visitors to enter a username and password (using HTTP Basic Auth). Since the site is served over HTTPS, credentials are encrypted in transit.
- Open your stack in the CloudFormation Console.
- Click Update > Use current template > Next.
- Under Password Protection, enter a Username and Password.
- Click through Next > Next > check the IAM acknowledgement > Submit.
Visitors will now be prompted for credentials when they access the site. To remove password protection, update the stack and clear both fields.
When a new version of Crystal is released, the public container image (latest tag) is updated automatically. To pick up the new version:
- Open your stack in the CloudFormation Console.
- Click Update > Use current template > Next.
- Change the Image Refresh Token field (under Advanced Options) to any new value (e.g.
2or today's date). - Click through Next > Next > check the IAM acknowledgement > Submit.
The stack update will pull the latest Crystal image. This typically takes 2-4 minutes.
To point the deployment at a different .crystalproj:
- Open your stack in the CloudFormation Console.
- Click Update > Use current template > Next.
- Change the Project S3 URL to the new S3 URL.
- Click through to submit.
- If you added a custom domain, remove it first.
- Open your stack in the CloudFormation Console.
- Click Delete.
- Confirm the deletion. All AWS resources created by the stack will be removed.
Note: This does not delete your
.crystalprojfrom S3. Your archived project data is preserved.
The CloudFormation template creates the following AWS resources:
| Resource | Purpose |
|---|---|
| Lambda Function | Runs Crystal to serve your archived site |
| Function URL | Provides a public HTTPS endpoint for the Lambda function |
| IAM Role | Grants the Lambda function permission to read your project from S3 and write logs to CloudWatch |
| URL Parser (helper Lambda) | Parses the S3 URL you provide into bucket name and key path (needed because CloudFormation's built-in string functions are limited) |
| Image Copier (helper Lambda) | Copies Crystal's public container image to a private ECR repository in your account (needed because Lambda can only run images from private ECR) |
| ECR Repository | Stores the private copy of the Crystal container image (crystal-on-aws) |
| CloudWatch Log Groups | Stores logs from the Lambda functions (auto-expires after 7 days) |
When a custom domain is added, the custom-domain-add.sh script additionally creates:
| Resource | Purpose |
|---|---|
| CloudFront Distribution | Routes traffic from your custom domain to the Lambda Function URL |
| ACM Certificate | Provides HTTPS for your custom domain |
Crystal on AWS is designed to be inexpensive for typical archival use (low-traffic sites served to occasional visitors).
The main cost components are:
- Lambda: Billed per request and per GB-second of compute time. A typical page load takes a few hundred milliseconds. Lambda has a generous free tier (1M requests/month).
- S3: Billed for storage and GET requests. Crystal reads objects on-demand as visitors browse.
- ECR: Billed for storage of the private container image copy (~350 MB, about $0.04/month).
- CloudWatch Logs: Log groups are configured with 7-day retention to limit costs.
- CloudFront (custom domain only): Billed per request and per GB of data transfer. Has a free tier (1 TB/month).
For a low-traffic archived site, expect costs on the order of a few cents per month beyond S3 storage for the project itself. The main ongoing cost is typically S3 storage for your .crystalproj.
This section covers workflows for developers working on the Crystal on AWS codebase itself, using scripts in src/crystal_on_aws/.
Use deploy-image.sh to build a new container image and push it to ECR. It automatically refreshes all Lambda functions whose stacks reference the pushed image.
# Build and push to your private ECR; refresh all stacks using it
src/crystal_on_aws/deploy-image.sh
# Build and push to public ECR with the "dev" tag; refresh all stacks using it
src/crystal_on_aws/deploy-image.sh --public --dev
# Build and push to public ECR with the release tag + "latest"; refresh all stacks using it
src/crystal_on_aws/deploy-image.sh --public --releaseFor local development it is frequently useful to build/push Crystal code changes
to a private ECR image. After your initial push, use switch-stack-version.sh (mentioned below) to switch one of your existing stacks to use your private ECR
version of Crystal.
There is no deploy-cf-template.sh target for private stacks. Update the template directly via the AWS Console or CLI:
AWS Console: Open the stack → Update → Replace current template → upload src/crystal_on_aws/crystal.cloudformation.yaml → keep all existing parameter values → submit.
CLI:
STACK_NAME=<stack-name>
PARAM_KEYS=$(aws cloudformation describe-stacks \
--stack-name "${STACK_NAME}" \
--query 'Stacks[0].Parameters[].ParameterKey' \
--output text)
PARAMS=()
for KEY in ${PARAM_KEYS}; do
PARAMS+=("ParameterKey=${KEY},UsePreviousValue=true")
done
aws cloudformation update-stack \
--stack-name "${STACK_NAME}" \
--template-body file://src/crystal_on_aws/crystal.cloudformation.yaml \
--parameters "${PARAMS[@]}" \
--capabilities CAPABILITY_NAMED_IAMUse deploy-cf-template.sh to upload the template to S3, where the "Launch Stack" buttons reference it:
src/crystal_on_aws/deploy-cf-template.sh --public --dev # Updates the Dev launch button
src/crystal_on_aws/deploy-cf-template.sh --public --release # Updates the Stable launch buttonNote: This only uploads the template to S3. It does not update any already-deployed stacks. Each existing stack continues to use its own copy of the template until manually updated.
Use switch-stack-version.sh to point an existing CloudFormation stack at a different Crystal image — without rebuilding or redeploying anything:
# Switch to your locally-built private ECR image
src/crystal_on_aws/switch-stack-version.sh <stack-name>
# Switch to the public dev image (main branch)
src/crystal_on_aws/switch-stack-version.sh <stack-name> --public --dev
# Switch to the public release image (latest stable)
src/crystal_on_aws/switch-stack-version.sh <stack-name> --public --releaseImageRefreshToken is always bumped, so the stack will re-pull the image even if the tag hasn't changed.
