How I Structure Angular + Docker + AI Projects
Every time I start a new Angular project, I used to waste 2-3 hours on the same boring setup:
- Folder structure? Let me think again...
- Docker? Copy from the last project and fix the broken parts...
- AI integration? Search Stack Overflow for 30 minutes...
- CI/CD? Another hour of YAML debugging...
After 14 years building software across oil & gas, critical infrastructure, and intelligent transport systems, I finally created the setup I wish I had on day one.
Here's exactly how I structure it — and why.
🏗️ The Architecture: Core / Shared / Features
src/app/
├── core/ → Singleton services, interceptors (loaded ONCE)
├── shared/ → Reusable components (used everywhere)
├── features/ → Lazy-loaded pages (loaded on demand)
├── app.config.ts → Standalone bootstrap
└── app.routes.ts → Route definitions
Why this pattern?
-
core/= things that exist once (HTTP interceptors, auth, AI service) -
shared/= things used in multiple features (header, sidebar, buttons) -
features/= each page is independent and lazy-loaded
This is not my opinion — this is the pattern that scales from a side project to a 50-developer team. I've used it in airports, highways, and oil rigs.
🤖 AI Integration in 30 Lines
The key insight: don't overthink it. You need one service.
@Injectable({ providedIn: 'root' })
export class AiService {
private readonly http = inject(HttpClient);
chat(messages: ChatMessage[], apiKey: string): Observable<string> {
const headers = new HttpHeaders({
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
});
return this.http
.post<ChatCompletionResponse>(
'https://api.openai.com/v1/chat/completions',
{
model: 'gpt-4o-mini',
messages,
max_tokens: 2048,
},
{ headers }
)
.pipe(map((res) => res.choices[0]?.message?.content ?? ''));
}
}
Then in any component:
this.ai.chat([
{ role: 'user', content: 'Explain Docker in 3 sentences' }
], apiKey).subscribe(response => console.log(response));
That's it. No complex abstractions. No wrapper libraries. Just Angular's HttpClient talking to OpenAI.
⚠️ In production, route calls through your backend. Never expose API keys in the frontend.
🐳 Docker: Multi-Stage Build
# Stage 1: Build
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
COPY . .
RUN npm run build:prod
# Stage 2: Serve
FROM nginx:alpine AS production
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist/app/browser /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Result: ~25MB production image. Fast to deploy, tiny to store.
I pair this with a docker-compose.yml that gives you both dev and prod:
# Production
docker-compose up --build
# Development (with hot reload)
docker-compose --profile dev up app-dev
⚡ The Details That Matter
Path Aliases (no more ../../../)
// tsconfig.json
"paths": {
"@core/*": ["src/app/core/*"],
"@shared/*": ["src/app/shared/*"],
"@features/*": ["src/app/features/*"]
}
Now your imports look like:
import { AiService } from '@core/services/ai.service';
Instead of:
import { AiService } from '../../../core/services/ai.service';
Signals for UI State
export class LoadingService {
private requestCount = signal(0);
readonly isLoading = computed(() => this.requestCount() > 0);
}
Angular Signals > RxJS BehaviorSubjects for simple UI state. Less boilerplate, better performance.
HTTP Interceptors (Functional)
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
// Handle 401, 429, 500, etc.
notification.error(getErrorMessage(error));
return throwError(() => error);
})
);
};
No classes. No implements HttpInterceptor. Just a function. This is Angular 17+.
📁 The Full Stack
| Layer | Tech |
|---|---|
| Framework | Angular 17+ (standalone, signals, new control flow) |
| Containerization | Docker multi-stage + Docker Compose |
| AI | OpenAI API (gpt-4o-mini) |
| Web Server | Nginx (gzip, caching, security headers) |
| CI/CD | GitHub Actions |
| Styling | SCSS + CSS custom properties (dark/light theme) |
🎯 TL;DR
After 14 years and dozens of projects across enterprise, infrastructure, and industrial domains, this is the setup I always come back to:
- Core/Shared/Features architecture with lazy loading
- Standalone components (no NgModules)
- Signals for UI state
- One clean AI service — no over-abstraction
- Multi-stage Docker build (~25MB image)
- Path aliases for clean imports
- Functional interceptors for HTTP
I packaged all of this into a ready-to-use starter kit. If you want to skip the 2-3 hours of setup and start building immediately:
👉 Angular + Docker + AI Starter Kit on Gumroad
Clone. Run. Ship.
What's your go-to Angular project structure? Let me know in the comments!
Top comments (0)