Deployment Guide (Non-Production)¶
Single EC2 instance running the full Floh stack via Docker Compose with Caddy for automatic HTTPS.
Architecture¶
Internet
│
├─ :443 ──► Caddy (TLS termination, Let's Encrypt)
│ ├─ floh-dev.example.com/api/* ──► server:3000
│ ├─ floh-dev.example.com/* ──► web:8080 (nginx)
│ ├─ portal.example.com/api/* ──► portal-bff:3001
│ └─ portal.example.com/* ──► portal-web:8080 (nginx)
│
└─ :8025 ──► MailHog UI (restrict to your IP)
Prerequisites¶
- AWS account
- A domain name with DNS you can control
- GitHub repo:
github.com/AxleResearch/floh
Step 1: Launch EC2 Instance¶
- Instance type:
t3.medium(2 vCPU, 4 GB RAM) - AMI: Ubuntu 24.04 LTS
- Storage: 30 GB gp3 EBS
- Key pair: Create or select one (save the
.pemfile)
Step 2: Security Group¶
Allow inbound:
| Port | Protocol | Source | Purpose |
|---|---|---|---|
| 22 | TCP | Your IP | SSH |
| 80 | TCP | Anywhere | HTTP (ACME challenges + redirect) |
| 443 | TCP | Anywhere | HTTPS |
| 8025 | TCP | Your IP | MailHog UI (optional) |
Step 3: Elastic IP¶
Allocate an Elastic IP and associate it with your instance. This gives a stable address that survives reboots.
Step 4: DNS¶
Create A records pointing to the Elastic IP:
floh-dev.example.com→<ELASTIC_IP>portal.floh-dev.example.com→<ELASTIC_IP>
Caddy will auto-provision Let's Encrypt certificates once DNS resolves.
Step 5: Install Docker¶
ssh -i your-key.pem ubuntu@<ELASTIC_IP>
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker ubuntu
newgrp docker
docker --version
docker compose version
Step 6: Authenticate Docker to GHCR¶
Create a GitHub PAT (classic) with the read:packages scope, then on the instance:
Step 7: Create Deployment Directory and Environment¶
Copy .env.deploy.example from the repo and save it as ~/floh/.env. Fill in real values — at minimum:
DEPLOY_DOMAINandDEPLOY_PORTAL_DOMAIN(your actual domains)DB_PASSWORD,JWT_SECRET,SESSION_SECRET(generate strong values)FRONTEND_URL,PORTAL_FRONTEND_URL,OIDC_REDIRECT_URI(match your domains)
Step 8: GitHub Repository Secrets¶
In Settings → Secrets → Actions, add:
| Secret | Value |
|---|---|
DEPLOY_SSH_KEY |
Contents of the .pem private key file |
DEPLOY_HOST |
The Elastic IP address |
DEPLOY_USER |
ubuntu |
Deploying¶
- Go to Actions → Deploy
- Click Run workflow
- Optionally specify a branch or tag (defaults to
main) - The workflow builds all 4 images, pushes to GHCR, and deploys to the EC2 instance
First deploy takes ~5 minutes (image pulls). Subsequent deploys are faster due to layer caching.
Operations¶
View logs¶
ssh -i your-key.pem ubuntu@<ELASTIC_IP>
cd ~/floh
docker compose -f docker-compose.deploy.yml logs -f # all services
docker compose -f docker-compose.deploy.yml logs -f server # single service
Restart a service¶
Update environment variables¶
Check service status¶
Access MailHog¶
Open http://<ELASTIC_IP>:8025 in your browser (if port 8025 is open in the security group).
Database access¶
Changing Domains Later¶
- Update DNS A records for the new domain
- Edit
~/floh/.env— updateDEPLOY_DOMAIN,DEPLOY_PORTAL_DOMAIN,FRONTEND_URL,PORTAL_FRONTEND_URL,OIDC_REDIRECT_URI, andALLOWED_PORTAL_ORIGINS - Run
docker compose -f docker-compose.deploy.yml up -dto restart with new domains - Caddy auto-provisions new Let's Encrypt certificates
Cost Estimate¶
| Resource | Monthly Cost |
|---|---|
| EC2 t3.medium | ~$33 |
| EBS 30 GB gp3 | ~$2.50 |
| Elastic IP (attached) | Free |
| Data transfer | ~$1-5 |
| GHCR | Free (included in GitHub plan) |
| Total | ~$37-41 |