Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,24 @@ docker-compose logs -f
docker-compose down
```

### Testnet Compose Deployment

For testnet deployment readiness, use the dedicated compose file:

```bash
# Build and run the testnet stack
docker compose -f docker-compose.testnet.yml up -d --build

# Validate backend health
curl http://localhost:3002/health

# Check service status
docker compose -f docker-compose.testnet.yml ps

# Stop testnet stack
docker compose -f docker-compose.testnet.yml down
```

### Services

| Service | Port | Description |
Expand Down
1 change: 0 additions & 1 deletion backend/.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ dist
build

# Prisma
prisma/migrations
prisma/dev.db

# Environment files
Expand Down
71 changes: 45 additions & 26 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,44 +1,63 @@
# Stage 1: Build
FROM node:20-alpine AS builder
# -----------------------------------------------------------------------------
# Stage 1: Shared base image
# -----------------------------------------------------------------------------
FROM node:20-alpine AS base

WORKDIR /app

# Copy package files
COPY package*.json ./
# -----------------------------------------------------------------------------
# Stage 2: Install dependencies once (cached by package lock changes)
# -----------------------------------------------------------------------------
FROM base AS deps

ENV NODE_ENV=development

# Install dependencies
# Copy only dependency manifests first to maximize Docker cache reuse
COPY package*.json ./
RUN npm ci

# Copy source code
COPY . .
# -----------------------------------------------------------------------------
# Stage 3: Build application and prune dev dependencies
# -----------------------------------------------------------------------------
FROM deps AS build

# Build TypeScript
RUN npm run build
# Copy application source after dependency installation to preserve cache layers
COPY . .

# Stage 2: Production
FROM node:20-alpine AS production
# Build TypeScript output and generate Prisma client artifacts.
# Afterwards prune dev dependencies to keep runtime image smaller.
RUN npm run build \
&& npx prisma generate \
&& npm prune --omit=dev \
&& npm cache clean --force

WORKDIR /app
# -----------------------------------------------------------------------------
# Stage 4: Runtime image (small, secure, production-only)
# -----------------------------------------------------------------------------
FROM base AS runtime

# Copy package files
COPY package*.json ./
ENV NODE_ENV=production

# Install production dependencies only
RUN npm ci --only=production
# Runtime utilities:
# - dumb-init for proper signal handling and zombie reaping
# - wget for container health checks
RUN apk add --no-cache dumb-init wget

# Copy built files from builder
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/prisma ./prisma
# Copy only runtime assets from build stage
COPY --from=build /app/package*.json ./
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY --from=build /app/prisma ./prisma

# Generate Prisma client
RUN npx prisma generate
# Use non-root user provided by official Node image
USER node

# Expose port
# Expose backend port
EXPOSE 3002

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
# Health check endpoint used by orchestrators and compose
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3002/health || exit 1

# Start the application
CMD ["npm", "start"]
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/index.js"]
97 changes: 97 additions & 0 deletions docker-compose.testnet.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: anchorpoint-backend-testnet
ports:
- "3002:3002"
- "9464:9464"
environment:
- NODE_ENV=production
- PORT=3002
- DATABASE_URL=file:/app/data/testnet.db
- REDIS_URL=redis://redis:6379
- STELLAR_NETWORK=testnet
- STELLAR_PASSPHRASE=Test SDF Network ; September 2015
- QUEUE_CONCURRENCY=5
- JAEGER_ENDPOINT=http://jaeger:14268/api/traces
- PROMETHEUS_METRICS_PORT=9464
- OTEL_SERVICE_NAME=anchorpoint-backend-testnet
- OTEL_RESOURCE_ATTRIBUTES=service.name=anchorpoint-backend-testnet,environment=testnet
volumes:
- backend-testnet-data:/app/data
depends_on:
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3002/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
restart: unless-stopped
networks:
- anchorpoint-testnet-network

redis:
image: redis:7-alpine
container_name: anchorpoint-redis-testnet
command: ["redis-server", "--appendonly", "yes"]
ports:
- "6379:6379"
volumes:
- redis-testnet-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
start_period: 10s
restart: unless-stopped
networks:
- anchorpoint-testnet-network

jaeger:
image: jaegertracing/all-in-one:latest
container_name: anchorpoint-jaeger-testnet
ports:
- "16686:16686"
- "14268:14268"
- "14250:14250"
- "6831:6831/udp"
- "6832:6832/udp"
environment:
- COLLECTOR_OTLP_ENABLED=true
restart: unless-stopped
networks:
- anchorpoint-testnet-network

prometheus:
image: prom/prometheus:latest
container_name: anchorpoint-prometheus-testnet
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-testnet-data:/prometheus
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--storage.tsdb.retention.time=200h"
- "--web.enable-lifecycle"
restart: unless-stopped
networks:
- anchorpoint-testnet-network

volumes:
backend-testnet-data:
driver: local
redis-testnet-data:
driver: local
prometheus-testnet-data:
driver: local

networks:
anchorpoint-testnet-network:
driver: bridge
20 changes: 20 additions & 0 deletions infra/k8s/cert-manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,26 @@
# curl -v https://your-hostname.example.com
# =============================================================================

NGINX ingress controller configuration for this cert-manager setup now lives in:

- `infra/k8s/ingress-nginx/values-testnet.yaml`
- `infra/k8s/ingress-nginx/anchorpoint-testnet-ingress.yaml`

Apply ingress after cert-manager and certificate resources are ready so TLS secrets
can be attached without repeated reconciliation errors:

```bash
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespace \
-f infra/k8s/ingress-nginx/values-testnet.yaml

kubectl apply -f infra/k8s/ingress-nginx/anchorpoint-testnet-ingress.yaml
kubectl get ingress -n anchorpoint-testnet
```

# =============================================================================
# Implementation Notes:
# =============================================================================
Expand Down
49 changes: 49 additions & 0 deletions infra/k8s/ingress-nginx/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# NGINX Ingress Controller Setup (Testnet)

This directory contains the NGINX ingress-controller configuration and ingress
resource required for AnchorPoint testnet routing.

## Files

- `values-testnet.yaml`: Helm values for installing `ingress-nginx`
- `anchorpoint-testnet-ingress.yaml`: Ingress routing for `api.anchorpoint-testnet.example.com`

## Manual QA Steps

1. Install or upgrade ingress-nginx with testnet values:
```bash
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespace \
-f infra/k8s/ingress-nginx/values-testnet.yaml
```

2. Verify controller pods and external service:
```bash
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx
```

3. Apply AnchorPoint ingress resource:
```bash
kubectl apply -f infra/k8s/ingress-nginx/anchorpoint-testnet-ingress.yaml
```

4. Verify ingress status and routing:
```bash
kubectl get ingress -n anchorpoint-testnet
kubectl describe ingress anchorpoint-api-ingress -n anchorpoint-testnet
```

5. Validate TLS and endpoint:
```bash
kubectl get secret anchorpoint-api-tls -n anchorpoint-testnet
curl -v https://api.anchorpoint-testnet.example.com/health
```

6. Roll back if needed:
```bash
kubectl delete -f infra/k8s/ingress-nginx/anchorpoint-testnet-ingress.yaml
helm uninstall ingress-nginx -n ingress-nginx
```
38 changes: 38 additions & 0 deletions infra/k8s/ingress-nginx/anchorpoint-testnet-ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# =============================================================================
# AnchorPoint Testnet Ingress
# =============================================================================
# Routes external HTTPS traffic from the NGINX ingress controller to the
# AnchorPoint testnet backend service.
# =============================================================================
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: anchorpoint-api-ingress
namespace: anchorpoint-testnet
labels:
app: anchorpoint
component: ingress
environment: testnet
annotations:
cert-manager.io/cluster-issuer: anchorpoint-staging-issuer
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/hsts: "true"
nginx.ingress.kubernetes.io/hsts-max-age: "31536000"
nginx.ingress.kubernetes.io/hsts-include-subdomains: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.anchorpoint-testnet.example.com
secretName: anchorpoint-api-tls
rules:
- host: api.anchorpoint-testnet.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: anchorpoint-worker-svc
port:
number: 3002
50 changes: 50 additions & 0 deletions infra/k8s/ingress-nginx/values-testnet.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# =============================================================================
# NGINX Ingress Controller Helm Values (Testnet)
# =============================================================================
# Install command:
# helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
# helm repo update
# helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
# --namespace ingress-nginx --create-namespace \
# -f infra/k8s/ingress-nginx/values-testnet.yaml
# =============================================================================

controller:
replicaCount: 2

ingressClassResource:
name: nginx
enabled: true
default: true
controllerValue: k8s.io/ingress-nginx

ingressClass: nginx
watchIngressWithoutClass: false

service:
type: LoadBalancer
externalTrafficPolicy: Local
annotations: {}

resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi

metrics:
enabled: true

config:
use-forwarded-headers: "true"
enable-real-ip: "true"
proxy-body-size: "20m"
ssl-redirect: "true"
hsts: "true"
hsts-max-age: "31536000"
hsts-include-subdomains: "true"

defaultBackend:
enabled: true