Adding a New Service¶
How to deploy a new application to the Docker Swarm cluster.
Prerequisites¶
- Source code on apps-dev1 (192.168.51.40) in
/opt/development/ - Docker image builds successfully
- DNS configured (Cloudflare for public, UDM for internal)
Step 1: Create Dockerfile¶
On apps-dev1, create a production Dockerfile:
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Step 2: Build and Push¶
# On apps-dev1
cd /opt/development/myapp
REGISTRY=registry.apps.jlwaller.com
docker build -t ${REGISTRY}/myapp:latest .
docker push ${REGISTRY}/myapp:latest
# Verify
curl -sk https://${REGISTRY}/v2/myapp/tags/list
Step 3: Create Stack File¶
On apps-gateway, create the stack file:
/opt/swarm/stacks/app-myapp/stack.myapp.yml:
version: '3.9'
services:
myapp:
image: registry.apps.jlwaller.com/myapp:latest
networks:
- public_net
- data_net
deploy:
replicas: 1
placement:
constraints:
- node.role == worker
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
order: start-first
restart_policy:
condition: any
delay: 5s
max_attempts: 5
resources:
limits:
memory: 256M
cpus: '0.5'
reservations:
memory: 128M
cpus: '0.25'
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`myapp.jlwaller.com`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
- "traefik.http.services.myapp.loadbalancer.server.port=3000"
- "traefik.docker.network=public_net"
networks:
public_net:
external: true
data_net:
external: true
Key Configuration Notes¶
- Networks: Use
public_netfor Traefik routing,data_netif the service needs database/cache access - Traefik labels: Must be under
deploy.labels(not service-levellabels) for Swarm mode - Port: Match the port your application listens on
- docker.network label: Required if the service is on multiple networks
Step 4: Configure DNS¶
For public domains (via Cloudflare):
- Add A record pointing to the Cloudflare proxy
- Enable orange cloud (proxied)
- Traefik will auto-obtain a Let's Encrypt certificate
For internal domains (via UDM):
- Add A record pointing to 192.168.50.10
- No Cloudflare proxy needed
Step 5: Deploy¶
Step 6: Verify¶
# Check service is running
docker service ls | grep myapp
docker service ps app-myapp_myapp
# Check logs
docker service logs -f app-myapp_myapp
# Check Traefik picked up the route
curl http://localhost:8080/api/http/routers | python3 -c "import sys,json; [print(r['rule']) for r in json.load(sys.stdin) if 'myapp' in r.get('name','')]"
# Test endpoint
curl -I https://myapp.jlwaller.com
Adding Database Access¶
If the service needs a database:
-
Create the database:
docker exec -it $(docker ps -qf name=data_postgres) psql -U postgres -c "CREATE DATABASE myapp;" docker exec -it $(docker ps -qf name=data_postgres) psql -U postgres -c "CREATE USER myapp_owner WITH PASSWORD 'secure_password';" docker exec -it $(docker ps -qf name=data_postgres) psql -U postgres -c "GRANT ALL ON DATABASE myapp TO myapp_owner;" -
Create Docker secret:
-
Add to stack file:
Adding BasicAuth Protection¶
Use the existing staging-auth middleware or create a new one:
deploy:
labels:
- "traefik.http.middlewares.myapp-auth.basicauth.users=user:$$2y$$05$$hashedpassword"
- "traefik.http.routers.myapp.middlewares=myapp-auth"
Generate the hash: htpasswd -nbB username password