Skip to main content
SkyTeam ROBLOX can be deployed using Docker containers for consistent, isolated environments. This guide covers Docker setup and deployment.

Prerequisites

Install Docker on your system:
# Ubuntu/Debian
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Install Docker Compose
sudo apt-get install docker-compose-plugin

# Add user to docker group
sudo usermod -aG docker $USER
Verify installation:
docker --version
docker compose version

Docker Compose Architecture

SkyTeam uses Docker Compose to orchestrate multiple services. The configuration is defined in docker-compose.yml:
docker-compose.yml
version: '3.8'

services:
  admin:      # Admin panel (Next.js)
  api:        # Backend API (Express)
  client:     # Discord bot
  web:        # Main website (Next.js)

networks:
  skyteam-network:  # Internal network

Service Overview

Admin Panel

Port: 3001
Tech: Next.js
Image: Built from apps/admin/Dockerfile

API Server

Port: 4000
Tech: Express
Image: Built from apps/api/Dockerfile

Discord Bot

Port: None
Tech: Discord.js
Image: Built from apps/client/Dockerfile

Web Frontend

Port: 3000
Tech: Next.js
Image: Built from apps/web/Dockerfile

Service Configuration Details

Admin Panel Service

docker-compose.yml
admin:
  build:
    context: .
    dockerfile: apps/admin/Dockerfile
  container_name: skyteam-admin
  ports:
    - "3001:3001"
  environment:
    - NODE_ENV=production
    - DATABASE_URL=${DATABASE_URL}
    - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
    - ADMIN_JWT_SECRET=${ADMIN_JWT_SECRET}
  networks:
    - skyteam-network
  restart: unless-stopped
Environment variables:
  • NODE_ENV - Set to production
  • DATABASE_URL - PostgreSQL connection string
  • NEXT_PUBLIC_API_URL - Public API endpoint
  • ADMIN_JWT_SECRET - JWT signing secret
Restart policy: unless-stopped - Restarts automatically except when manually stopped

API Server Service

docker-compose.yml
api:
  build:
    context: .
    dockerfile: apps/api/Dockerfile
  container_name: skyteam-api
  ports:
    - "4000:4000"
  environment:
    - NODE_ENV=production
    - DATABASE_URL=${DATABASE_URL}
    - PORT=4000
    - ADMIN_JWT_SECRET=${ADMIN_JWT_SECRET}
  networks:
    - skyteam-network
  restart: unless-stopped
Environment variables:
  • NODE_ENV - Production mode
  • DATABASE_URL - Database connection
  • PORT - API server port (4000)
  • ADMIN_JWT_SECRET - JWT verification secret

Discord Bot Service

docker-compose.yml
client:
  build:
    context: .
    dockerfile: apps/client/Dockerfile
  container_name: skyteam-client
  environment:
    - NODE_ENV=production
    - DATABASE_URL=${DATABASE_URL}
    - DISCORD_TOKEN=${DISCORD_TOKEN}
    - DISCORD_HOME_GUILD_ID=${DISCORD_HOME_GUILD_ID}
    - DISCORD_CLIENT_ID=${DISCORD_CLIENT_ID}
  networks:
    - skyteam-network
  restart: unless-stopped
Environment variables:
  • DISCORD_TOKEN - Bot authentication token
  • DISCORD_HOME_GUILD_ID - Discord server ID
  • DISCORD_CLIENT_ID - Discord application ID
Note: No exposed ports - bot connects to Discord’s API

Web Frontend Service

docker-compose.yml
web:
  build:
    context: .
    dockerfile: apps/web/Dockerfile
  container_name: skyteam-web
  ports:
    - "3000:3000"
  environment:
    - NODE_ENV=production
    - DATABASE_URL=${DATABASE_URL}
    - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
  networks:
    - skyteam-network
  restart: unless-stopped
Environment variables:
  • NEXT_PUBLIC_API_URL - API endpoint for browser

Deployment Steps

1

Prepare Environment Variables

Create a .env file in the project root with production values:
.env
DATABASE_URL="postgresql://user:password@your-db-host:5432/skyteam"
DISCORD_TOKEN="your-production-discord-token"
DISCORD_HOME_GUILD_ID="your-guild-id"
DISCORD_CLIENT_ID="your-client-id"
NEXT_PUBLIC_API_URL="https://api.yourdomain.com"
ADMIN_JWT_SECRET="your-production-secret"
NODE_ENV="production"
Use production-grade secrets. Never use development credentials in production!
2

Build Docker Images

Build all service images:
docker compose build
This will:
  • Build each Dockerfile
  • Install dependencies with pnpm 10.5.2
  • Compile TypeScript to JavaScript
  • Create optimized production images
Build times can be 5-10 minutes on first run. Subsequent builds are faster due to Docker layer caching.
3

Start All Services

Launch all containers:
docker compose up -d
The -d flag runs containers in detached mode (background).
Verify all services are running:
docker compose ps
4

Initialize Database

Push database schema (one-time setup):
# Run db:push from API container
docker compose exec api pnpm --filter @skyteam/database db:push
Or connect to your external database and run migrations.
5

Verify Deployment

Check service health:View logs:
docker compose logs -f

Docker Commands Reference

# Start all services
docker compose up -d

# Start specific service
docker compose up -d api

# Start with build
docker compose up -d --build

Dockerfile Structure

Each service uses a multi-stage Dockerfile for optimized builds. Example from apps/api/Dockerfile:
apps/api/Dockerfile
# Stage 1: Base build environment
FROM node:20-alpine AS base

RUN corepack enable && corepack prepare [email protected] --activate
WORKDIR /app

# Copy workspace configuration
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
COPY turbo.json ./

# Copy package.json files
COPY packages/database/package.json ./packages/database/
COPY apps/api/package.json ./apps/api/

# Install dependencies
RUN pnpm install --frozen-lockfile --include-workspace-root

# Copy source code
COPY packages/database ./packages/database
COPY apps/api ./apps/api

# Build packages
RUN pnpm --filter @skyteam/database build
RUN pnpm --filter @skyteam/api build

# Stage 2: Production runtime
FROM node:20-alpine AS runner

WORKDIR /app
ENV NODE_ENV=production

RUN corepack enable && corepack prepare [email protected] --activate

# Copy package files
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
COPY packages/database/package.json ./packages/database/
COPY apps/api/package.json ./apps/api/

# Install production dependencies only
RUN pnpm install --prod --frozen-lockfile

# Copy built artifacts
COPY --from=base /app/packages/database/dist ./packages/database/dist
COPY --from=base /app/apps/api/dist ./apps/api/dist

WORKDIR /app/apps/api

EXPOSE 4000

CMD ["pnpm", "start"]
Key features:
  • Multi-stage build - Separates build and runtime environments
  • Layer caching - Optimizes rebuild times
  • Production-only deps - Smaller image size
  • Node 20 Alpine - Minimal base image (~150MB vs 1GB)

Network Configuration

Services communicate via the skyteam-network bridge network:
docker-compose.yml
networks:
  skyteam-network:
    driver: bridge
Internal DNS:
  • Services can reference each other by service name
  • Example: API can connect to http://web:3000
External access:
  • Only mapped ports are accessible from host
  • Ports 3000, 3001, 4000 exposed to host machine

Production Deployment

Using External Database

For production, use a managed PostgreSQL service:
.env
DATABASE_URL="postgresql://postgres:[PASSWORD]@db.[PROJECT].supabase.co:5432/postgres"

Environment-Specific Configs

Create separate compose files:
docker-compose.prod.yml
version: '3.8'

services:
  api:
    environment:
      - LOG_LEVEL=info
      - RATE_LIMIT_ENABLED=true
  
  web:
    environment:
      - NEXT_TELEMETRY_DISABLED=1
Deploy with:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Health Checks

Add health checks to services:
docker-compose.yml
api:
  healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 40s

Reverse Proxy (Production)

Use Nginx or Traefik as reverse proxy:
docker-compose.yml
nginx:
  image: nginx:alpine
  ports:
    - "80:80"
    - "443:443"
  volumes:
    - ./nginx.conf:/etc/nginx/nginx.conf:ro
    - ./ssl:/etc/nginx/ssl:ro
  depends_on:
    - api
    - web
    - admin
  networks:
    - skyteam-network

Monitoring & Logs

View Logs

# Real-time logs for all services
docker compose logs -f

# Service-specific logs
docker compose logs -f api

# With timestamps
docker compose logs -f -t api

Container Stats

# Resource usage
docker stats

# Specific service
docker stats skyteam-api

Log Management

Configure log rotation:
/etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Troubleshooting

Clear Docker cache and rebuild:
docker compose build --no-cache
docker system prune -a
Check logs for errors:
docker compose logs api
Common issues:
  • Missing environment variables
  • Port conflicts (check with netstat -tuln)
  • Database connection failures
Verify network configuration:
docker network ls
docker network inspect skyteam-network
Check if services are on the same network.
Clean up unused Docker resources:
# Remove stopped containers
docker container prune

# Remove unused images
docker image prune -a

# Remove all unused resources
docker system prune -a --volumes
Docker volumes may have sync issues. Use bind mounts in dev:
docker-compose.dev.yml
services:
  api:
    volumes:
      - ./apps/api:/app/apps/api
      - /app/node_modules  # Don't override node_modules

Security Best Practices

1

Use Secret Management

Don’t store secrets in .env files in production. Use:
  • Docker secrets
  • AWS Secrets Manager
  • HashiCorp Vault
  • Environment variables from CI/CD
2

Run as Non-Root User

Add to Dockerfile:
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
USER nodejs
3

Scan Images for Vulnerabilities

docker scan skyteam-api:latest
4

Use Read-Only Filesystem

docker-compose.yml
api:
  read_only: true
  tmpfs:
    - /tmp

Next Steps

Configuration

Configure environment variables

Development

Start developing features

Build docs developers (and LLMs) love