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