Skip to content

Docker Deployment

This guide covers deploying License Monitor and License Server Detail using Docker containers.

Docker deployment provides:

  • Consistent environments across development and production
  • Easy scaling and orchestration
  • Simplified dependency management
  • Portable deployments
# Dockerfile for License Monitor
FROM debian:bookworm-slim as runtime
# Install runtime dependencies
RUN apt-get update && apt-get install -y \
ca-certificates \
python3 \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN useradd -r -s /bin/false -u 1000 license-monitor
# Copy binary from release
COPY --chown=license-monitor:license-monitor license_monitor /usr/local/bin/
RUN chmod +x /usr/local/bin/license_monitor
# Copy configuration
COPY --chown=license-monitor:license-monitor config.toml /etc/license-monitor/
COPY --chown=license-monitor:license-monitor servers/ /etc/license-monitor/servers/
# Create directories
RUN mkdir -p /var/log/license-monitor \
&& chown license-monitor:license-monitor /var/log/license-monitor
# Switch to non-root user
USER license-monitor
# Expose API port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8080/api/health || exit 1
# Run License Monitor
ENTRYPOINT ["/usr/local/bin/license_monitor"]
CMD ["--config", "/etc/license-monitor/config.toml", "--daemon"]
Terminal window
# Download the latest release binary
curl -L -o license_monitor \
"https://github.com/keithce/license_monitor/releases/latest/download/license_monitor-linux-x86_64"
# Build the Docker image
docker build -t license-monitor:latest .
# Dockerfile for License Server Detail
FROM oven/bun:1.3 AS base
# Install dependencies
FROM base AS deps
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
# Build application
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build environment variables (non-secret)
ARG NEXT_PUBLIC_API_BASE_URL
ARG NEXT_PUBLIC_API_TIMEOUT_MS=15000
ARG NEXT_PUBLIC_LOG_LEVEL=info
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
ENV NEXT_PUBLIC_API_TIMEOUT_MS=$NEXT_PUBLIC_API_TIMEOUT_MS
ENV NEXT_PUBLIC_LOG_LEVEL=$NEXT_PUBLIC_LOG_LEVEL
ENV NEXT_TELEMETRY_DISABLED=1
RUN bun run build
# Production image
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
# Create non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy built assets
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD curl -f http://localhost:3000/api/health || exit 1
CMD ["bun", "server.js"]
Terminal window
docker build \
--build-arg NEXT_PUBLIC_API_BASE_URL=https://api.example.com \
-t license-server-detail:latest .
docker-compose.yml
version: '3.8'
services:
license-monitor:
build:
context: ./license-monitor
dockerfile: Dockerfile
image: license-monitor:latest
container_name: license-monitor
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- ./config/license-monitor:/etc/license-monitor:ro
- license-monitor-logs:/var/log/license-monitor
environment:
- LICENSE_MONITOR_LOG_LEVEL=warn
- LICENSE_MONITOR_API_ENABLED=true
- LICENSE_MONITOR_BIND_ADDRESS=0.0.0.0
- ALLOW_PUBLIC_BIND=true
networks:
- license-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/api/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
license-server-detail:
build:
context: ./license-server-detail
dockerfile: Dockerfile
args:
- NEXT_PUBLIC_API_BASE_URL=http://license-monitor:8080
image: license-server-detail:latest
container_name: license-server-detail
restart: unless-stopped
ports:
- "3000:3000"
environment:
- LICENSE_MONITOR_BASE_URL=http://license-monitor:8080
- LICENSE_MONITOR_API_KEY=${LICENSE_MONITOR_API_KEY}
- AUTH_OKTA_ID=${AUTH_OKTA_ID}
- AUTH_OKTA_SECRET=${AUTH_OKTA_SECRET}
- AUTH_OKTA_ISSUER=${AUTH_OKTA_ISSUER}
- AUTH_SECRET=${AUTH_SECRET}
- NEXTAUTH_URL=${NEXTAUTH_URL}
depends_on:
license-monitor:
condition: service_healthy
networks:
- license-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
nginx:
image: nginx:alpine
container_name: nginx-proxy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./config/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./config/nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- license-server-detail
networks:
- license-network
volumes:
license-monitor-logs:
networks:
license-network:
driver: bridge
.env
# License Monitor
LICENSE_MONITOR_API_KEY=your-secure-api-key
# Okta Authentication
AUTH_OKTA_ID=your-okta-client-id
AUTH_OKTA_SECRET=your-okta-client-secret
AUTH_OKTA_ISSUER=https://your-org.okta.com/oauth2/default
# NextAuth
AUTH_SECRET=your-32-character-secret
NEXTAUTH_URL=https://dashboard.example.com
Terminal window
# Start all services
docker-compose up -d
# View logs
docker-compose logs -f
# View specific service logs
docker-compose logs -f license-monitor
Terminal window
# Stop all services
docker-compose down
# Stop and remove volumes
docker-compose down -v
Terminal window
# Pull latest images
docker-compose pull
# Rebuild and restart
docker-compose up -d --build
# Zero-downtime update (with multiple replicas)
docker-compose up -d --scale license-monitor=2
# Wait for new container to be healthy
docker-compose up -d --scale license-monitor=1
PathPurposeRecommended
/etc/license-monitor/ConfigurationRead-only bind mount
/var/log/license-monitor/Log filesNamed volume
/etc/license-monitor/servers/Parser scriptsRead-only bind mount
PathPurposeRecommended
/app/.next/cache/Build cacheNamed volume
/app/public/Static assetsRead-only bind mount
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/api/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s

Services communicate over the license-network bridge network using container names as hostnames:

license-server-detail → license-monitor:8080
nginx → license-server-detail:3000

Only expose necessary ports to the host:

ports:
- "443:443" # HTTPS via nginx
# Don't expose internal ports directly
Terminal window
# Scale License Monitor instances
docker-compose up -d --scale license-monitor=3
# Use load balancer for multiple instances
services:
license-monitor:
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M

Configure JSON logging for container environments:

config.toml
[daemon]
log_format = "json"
log_level = "info"
services:
license-monitor:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
labels: "application,environment"
labels:
application: "license-monitor"
environment: "production"
USER license-monitor # or nextjs
services:
license-monitor:
read_only: true
tmpfs:
- /tmp
volumes:
- license-monitor-logs:/var/log/license-monitor
services:
license-server-detail:
secrets:
- auth_secret
- okta_secret
secrets:
auth_secret:
external: true
okta_secret:
external: true