Skip to content

Manual Dockerfile Build

This guide covers building and running the Dreadnought images manually — without relying on docker compose build. This is useful for:

  • Pushing images to a private container registry
  • Custom CI/CD pipelines
  • Running images on container platforms that need pre-built images
  • Understanding exactly what's being built

Overview of the Images

Dreadnought has three Dockerfiles:

Image Source Purpose
API backend/Dockerfile FastAPI REST API server
Worker backend/Dockerfile.worker APScheduler background worker
Web frontend/Dockerfile Next.js frontend

All three images run as non-root users for security.


Step 1 — Clone the Repository

git clone https://github.com/dreadnought-0/Dreadnought-DDNS.git
cd Dreadnought-DDNS

Step 2 — Build the Images

Build each image individually:

# Build the API image
docker build -t dreadnought-api:latest ./backend -f ./backend/Dockerfile

# Build the Worker image
docker build -t dreadnought-worker:latest ./backend -f ./backend/Dockerfile.worker

# Build the Frontend image
docker build -t dreadnought-web:latest ./frontend -f ./frontend/Dockerfile

Or build all three in one go using docker compose:

docker compose build

Step 3 — Push to a Registry (Optional)

If you want to push to Docker Hub or a private registry:

# Tag for Docker Hub
docker tag dreadnought-api:latest yourusername/dreadnought-api:latest
docker tag dreadnought-worker:latest yourusername/dreadnought-worker:latest
docker tag dreadnought-web:latest yourusername/dreadnought-web:latest

# Push
docker push yourusername/dreadnought-api:latest
docker push yourusername/dreadnought-worker:latest
docker push yourusername/dreadnought-web:latest

For a private registry:

docker tag dreadnought-api:latest registry.yourdomain.com/dreadnought-api:latest
docker push registry.yourdomain.com/dreadnought-api:latest

Step 4 — Run the Containers Manually

If you want to run without docker compose, you can use docker run directly. However, the three containers need to share the ./data volume and be on the same network.

Create a shared network and volume

docker network create dreadnought-net
docker volume create dreadnought-data

Or use a host directory (simpler for most use cases):

mkdir -p ./data
chmod 777 ./data

Run the Worker (start this first — it initializes the database)

docker run -d \
  --name dreadnought-worker \
  --network dreadnought-net \
  --restart unless-stopped \
  -v "$(pwd)/data:/data" \
  -e DATABASE_URL=sqlite:////data/ddns.db \
  -e CF_API_TOKEN=your_token_here \
  -e POLL_INTERVAL_SECONDS=300 \
  -e IPV6_ENABLED=false \
  -e DISCORD_WEBHOOK_URL= \
  -e TZ=America/New_York \
  dreadnought-worker:latest

Run the API

docker run -d \
  --name dreadnought-api \
  --network dreadnought-net \
  --restart unless-stopped \
  -v "$(pwd)/data:/data" \
  -p 8081:8000 \
  -e DATABASE_URL=sqlite:////data/ddns.db \
  -e CF_API_TOKEN=your_token_here \
  -e ADMIN_EMAIL=you@example.com \
  -e ADMIN_PASSWORD=your_password \
  -e SECRET_KEY=your_secret_key \
  -e POLL_INTERVAL_SECONDS=300 \
  -e IPV6_ENABLED=false \
  -e DISCORD_WEBHOOK_URL= \
  -e TZ=America/New_York \
  dreadnought-api:latest

Run the Frontend

docker run -d \
  --name dreadnought-web \
  --network dreadnought-net \
  --restart unless-stopped \
  -p 8082:3000 \
  -e NEXT_PUBLIC_API_URL=http://localhost:8081 \
  -e NODE_ENV=production \
  dreadnought-web:latest

Step 5 — Verify

# Check all containers are running
docker ps | grep dreadnought

# Test API health
curl http://localhost:8081/health

# View logs
docker logs dreadnought-api
docker logs dreadnought-worker
docker logs dreadnought-web

Using Pre-Built Images in docker-compose.yml

If you've pushed to a registry, you can modify docker-compose.yml to use the pre-built images instead of building from source:

services:
  api:
    image: yourusername/dreadnought-api:latest   # instead of build: ...
    ports:
      - "8081:8000"
    volumes:
      - ./data:/data
    environment:
      - DATABASE_URL=sqlite:////data/ddns.db
      - CF_API_TOKEN=${CF_API_TOKEN}
      - ADMIN_EMAIL=${ADMIN_EMAIL:-admin@localhost.local}
      - ADMIN_PASSWORD=${ADMIN_PASSWORD:-ChangeMe!}
      - SECRET_KEY=${SECRET_KEY}
      - POLL_INTERVAL_SECONDS=${POLL_INTERVAL_SECONDS:-300}
      - IPV6_ENABLED=${IPV6_ENABLED:-false}
      - DISCORD_WEBHOOK_URL=${DISCORD_WEBHOOK_URL:-}
      - TZ=${TZ:-America/Denver}
    restart: unless-stopped

  worker:
    image: yourusername/dreadnought-worker:latest
    volumes:
      - ./data:/data
    environment:
      - DATABASE_URL=sqlite:////data/ddns.db
      - CF_API_TOKEN=${CF_API_TOKEN}
      - POLL_INTERVAL_SECONDS=${POLL_INTERVAL_SECONDS:-300}
      - IPV6_ENABLED=${IPV6_ENABLED:-false}
      - DISCORD_WEBHOOK_URL=${DISCORD_WEBHOOK_URL:-}
      - TZ=${TZ:-America/Denver}
    restart: unless-stopped

  web:
    image: yourusername/dreadnought-web:latest
    ports:
      - "8082:3000"
    environment:
      - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://localhost:8081}
      - NODE_ENV=production
    depends_on:
      - api
    restart: unless-stopped

Then run with:

docker compose up -d   # pulls from registry, no build step

Understanding What Each Dockerfile Does

backend/Dockerfile (API)

Base: python:3.12-slim
- Installs wget (for health check)
- Creates appuser (UID 1000, GID 1000)
- Installs Python dependencies from requirements.txt
- Copies application code
- Creates /data directory owned by appuser
- Switches to appuser (non-root)
- Exposes port 8000
- Starts: uvicorn main:app --host 0.0.0.0 --port 8000

backend/Dockerfile.worker

Base: python:3.12-slim
- Creates appuser (same UID/GID as API for volume sharing)
- Installs Python dependencies
- Copies application code
- Creates /data directory
- Switches to appuser
- Starts: python worker.py

frontend/Dockerfile (multi-stage)

Stage 1 (builder):
  Base: node:18-alpine
  - npm install
  - npm run build (creates standalone Next.js output)

Stage 2 (runner):
  Base: node:18-alpine
  - Creates nextjs user (UID 1001)
  - Copies built output from stage 1
  - Runs as nextjs (non-root)
  - Exposes port 3000
  - Starts: node server.js

The multi-stage frontend build keeps the final image small — dev dependencies and the build toolchain are discarded.


Specifying a Version Tag

When building or pushing, use version tags alongside latest for reproducible deployments:

VERSION=1.2.3

docker build -t dreadnought-api:${VERSION} -t dreadnought-api:latest ./backend -f ./backend/Dockerfile
docker push yourusername/dreadnought-api:${VERSION}
docker push yourusername/dreadnought-api:latest

Cross-Platform Builds (ARM / Raspberry Pi)

To build for Raspberry Pi (ARM64) on an x86 machine:

# Install buildx if not already available
docker buildx create --use

# Build for ARM64
docker buildx build --platform linux/arm64 \
  -t yourusername/dreadnought-api:latest \
  ./backend -f ./backend/Dockerfile \
  --push

docker buildx build --platform linux/arm64 \
  -t yourusername/dreadnought-worker:latest \
  ./backend -f ./backend/Dockerfile.worker \
  --push

docker buildx build --platform linux/arm64 \
  -t yourusername/dreadnought-web:latest \
  ./frontend -f ./frontend/Dockerfile \
  --push

Or build for multiple platforms at once:

docker buildx build --platform linux/amd64,linux/arm64 \
  -t yourusername/dreadnought-api:latest \
  ./backend -f ./backend/Dockerfile \
  --push