@ossy/deployment-tools
v1.40.2
Published
Collection of scripts and tools to aid deployment of containers and static files to Amazon Web Services through GitHub Actions
Readme
@ossy/deployment-tools
Infrastructure and CDK tooling for the Ossy platform EC2 host.
The host runs Caddy as a reverse proxy plus several Docker containers managed by systemd. Apps are deployed by running ossy app publish.
Architecture
Internet → Caddy (:443)
├── api.ossy.se → ossy-api (:3001)
├── ossy.se → ossy-website-ossy (:3002) ← temporary
├── www.plexus-sanitas.com → ossy-website-plexus-sanitas (:3003) ← temporary
└── *.* (on-demand TLS) → ossy-platform (:3000)
└── loads build from CMS on first requestCaddy uses on-demand TLS: before issuing a certificate for any domain, it calls GET http://localhost:3001/api/v0/apps/ask?domain=<domain>. If the domain is registered (i.e. ossy app publish has been run for it), Caddy issues the cert and proxies to the platform runtime.
Services
All services run as Docker containers managed by systemd. Service files are written to the EC2 host by CDK user data at instance creation.
| Service | Unit file | Port | Image |
|---|---|---|---|
| Caddy | caddy-route53.service | 80, 443 | — |
| Ossy API | ossy-api.service | 3001 | ghcr.io/ossy-se/api:latest |
| Ossy Platform | ossy-runtime.service | 3000 | ghcr.io/ossy-se/platform:latest |
| configured via services | <name>.service | per config | per config |
Additional services are generated automatically from the services array in platforms.json — see the Configuration section below.
Configuration — platforms.json
packages/infrastructure/platforms.json is the single source of truth for a platform. Each entry becomes a CDK Stage with its own set of stacks.
| Field | Required | Description |
|---|---|---|
| platformName | Yes | Unique name, used as the CDK stage name and CloudFormation stack prefix |
| awsAccountId | Yes | AWS account ID |
| awsKeyPairName | Yes | EC2 key pair name for SSH access |
| awsInstanceClass | No | EC2 instance class (e.g. t3, t4g, m5). Defaults to t3 |
| awsInstanceSize | No | EC2 instance size (e.g. small, medium, large). Defaults to small |
| sesDomains | No | Domains to configure SES email identity for |
| domains | No | Domains to create Route53 A records for, pointing to the EC2 Elastic IP. Supports wildcards (e.g. *.ossy.se). |
| services | No | Per-platform container services (see below) |
| dnsRecords | No | Additional DNS records by root domain (currently supports MX) |
| env | No | Environment variables written to /etc/environment on the EC2 host at boot. Never uploaded to S3. |
services — container/domain mapping
Services come in two flavours: HTTP (routed through Caddy) and TCP/UDP (raw socket, bypasses Caddy). Both generate a systemd unit that pulls the image on every restart and forwards /etc/environment to the container.
HTTP service (default)
Generates a Caddy reverse-proxy block with Route53 TLS. The container must listen on port 3000.
"services": [
{
"name": "ossy-website-ossy",
"domain": "ossy.se",
"image": "ghcr.io/ossy-se/website-ossy:latest"
}
]| Field | Description |
|---|---|
| name | Unique service name — Docker container name and systemd unit (<name>.service). |
| domain | Hostname routed by Caddy to this container (Route53 TLS via dns route53). |
| image | Docker image to pull and run. |
Host ports are auto-assigned starting at 3002, counting only HTTP services (TCP entries are skipped). Appending new HTTP entries is safe; avoid reordering existing ones.
TCP/UDP service
No Caddy block. Ports are mapped directly (HOST:CONTAINER) and opened in the EC2 security group. Use this for game servers or any other raw socket protocol.
"services": [
{
"name": "minecraft",
"type": "tcp",
"image": "itzg/minecraft-server",
"ports": [25565],
"protocol": "tcp"
}
]| Field | Description |
|---|---|
| name | Unique service name — Docker container name and systemd unit. |
| type | Must be "tcp" to enable this mode. |
| image | Docker image to pull and run. |
| ports | Array of port numbers to expose on the host and open in the security group. |
| protocol | "tcp" (default), "udp", or "both". |
Fixed built-in services (always present, not configurable via services):
ossy-api— the Ossy API (ghcr.io/ossy-se/api:latest, port 3001)ossy-runtime— the platform runtime serving all CMS-published apps (ghcr.io/ossy-se/platform:latest, port 3000, catch-all)
Custom domains: For customer-owned domains (e.g. woodhill.com), the customer adds an A record pointing to the Elastic IP at their registrar. No infrastructure change needed — Caddy's on-demand TLS issues the cert automatically once the domain is registered via ossy app publish.
Wildcard subdomains: Add *.ossy.se to domains for a Route53 wildcard A record. This covers any *.ossy.se subdomain without individual records.
MEDIA_REPOSITORY and MEDIA_CDN_DOMAIN_NAME are derived automatically from the provisioned S3 bucket and CloudFront distribution — do not set them in env.
To add a new platform, add an entry to platforms.json. No bucket name or CDN domain is needed; these are auto-generated by CloudFormation.
CDK Stacks
Each platform is a CDK Stage containing five stacks:
| Stack | Description |
|---|---|
| storage-static | S3 bucket (auto-named), daily backup plan, CloudFront CDN for /media. Kept separate from the EC2 stack so storage resources are never touched during instance updates. |
| deployment-target | EC2 instance with Elastic IP. Imports the S3 bucket from storage-static via cross-stack reference. |
| dns | Route53 A records for all domains in platforms.json, pointing to the Elastic IP. |
| ses | SES email sending identity and DKIM records. |
The Elastic IP ensures the EC2 instance can be replaced (instance type changes, user data updates) without breaking DNS or CloudFormation cross-stack exports.
Bootstrapping a new platform
cd packages/deployment-tools
npx cdk deploy 'ossybot/**' --profile ossybot --require-approval neverCDK provisions the instance, writes all service files, writes /etc/environment from platforms.json, and starts all services automatically. No manual SSH steps are required.
Note: EC2 user data only runs on first boot. To apply infrastructure changes to an existing instance you must terminate and recreate it, or SSH in and apply changes manually.
Deploying changes
Target a single platform:
npx cdk deploy 'ossybot/**' --profile ossybotTarget all platforms:
npx cdk deploy '**' --profile ossybotPreview changes without deploying:
npx cdk diff 'ossybot/**' --profile ossybotList all stacks:
npx cdk listRedeploying after a code change
All services pull the latest image from GHCR on every restart — no manual build step needed.
# API — push to main triggers CI → ghcr.io/ossy-se/api:latest
sudo systemctl restart ossy-api
# Platform runtime — merge to main in ossy monorepo → ghcr.io/ossy-se/platform:latest
sudo systemctl restart ossy-runtime
# website-ossy — push to main in website-ossy repo → ghcr.io/ossy-se/website-ossy:latest
sudo systemctl restart ossy-website-ossy
# website-plexus-sanitas — push to main → ghcr.io/ossy-se/website-plexus-sanitas:latest
sudo systemctl restart ossy-website-plexus-sanitasEnvironment variables
Defined in the env block of platforms.json, written to /etc/environment at boot. The following variables are injected automatically by CDK and should not be set manually:
| Variable | Source |
|---|---|
| MEDIA_REPOSITORY | S3 bucket name from storage-static stack |
| MEDIA_CDN_DOMAIN_NAME | CloudFront domain from storage-static stack |
All other env vars:
| Variable | Used by | Description |
|---|---|---|
| DB_URL | API | Atlas MongoDB connection string |
| DB_NAME | API | MongoDB database name |
| TOKEN_SECRET | API | JWT signing secret |
| AWS_ACCESS_KEY_ID | API | AWS credentials for S3 |
| AWS_SECRET_ACCESS_KEY | API | AWS credentials for S3 |
| OSSY_API_KEY | Platform, websites | API JWT for CMS reads |
| OSSY_COOKIE_SECRET | Websites | Cookie signing secret |
| OPENAI_API_KEY | Worker | OpenAI API key |
| OPENAI_ORGANIZATION | Worker | OpenAI organisation ID |
Useful commands
SSH into instance
ssh -i path/to/keys ubuntu@<elastic-ip>View service logs
journalctl -u ossy-runtime.service -n 200 -f
journalctl -u ossy-api.service -n 200 -f
journalctl -u ossy-website-ossy.service -n 200 -f
journalctl -u caddy-route53.service -n 200 -fView running containers
docker psView container logs
docker logs ossy-runtime
docker logs ossy-api
docker logs ossy-website-ossyReload Caddy config
sudo systemctl reload caddy-route53.service