Add step 03: Docker, Deployment & First Live App

- Introduced a new section on building Docker images and deploying the fitness app.
- Added detailed explanations of Docker concepts, including images, containers, and volumes.
- Included three Dockerfiles for the backend and frontend, with line-by-line explanations.
- Updated main.tex to include step_03.tex.
- Modified main.toc to reflect new sections and subsections.
- Updated main.fls and main.log to include new font inputs and log entries related to the new content.
- Adjusted font configurations for FiraMono and FiraSans in main.fls.
- Updated PDF and synctex files to reflect changes in the document structure.
This commit is contained in:
2026-05-06 17:27:49 +02:00
parent 8a4ed88b93
commit 0e9377739e
9 changed files with 532 additions and 45 deletions
+40 -3
View File
@@ -69,12 +69,49 @@
\@writefile{toc}{\contentsline {subsubsection}{\numberline {2.5.3}Firewall aktivieren}{10}{subsubsection.2.5.3}\protected@file@percent }
\@writefile{lol}{\contentsline {lstlisting}{\numberline {21}Firewall aktivieren}{10}{lstlisting.21}\protected@file@percent }
\BKM@entry{id=19,dest={73756273656374696F6E2E322E36},srcline={179}}{5C3337365C3337375C3030305A5C303030755C303030735C303030615C3030306D5C3030306D5C303030655C3030306E5C303030665C303030615C303030735C303030735C303030755C3030306E5C30303067}
\BKM@entry{id=20,dest={73656374696F6E2E33},srcline={5}}{5C3337365C3337375C303030445C3030306F5C303030635C3030306B5C303030655C303030725C3030302D5C303030495C3030306D5C303030615C303030675C303030655C303030735C3030305C3034305C303030625C303030615C303030755C303030655C3030306E5C3030305C3034305C303030755C3030306E5C303030645C3030305C3034305C303030415C303030705C303030705C3030305C3034305C303030645C303030655C303030705C3030306C5C3030306F5C303030795C303030655C3030306E}
\@writefile{toc}{\contentsline {subsubsection}{\numberline {2.5.4}Konfiguration überprüfen}{11}{subsubsection.2.5.4}\protected@file@percent }
\@writefile{lol}{\contentsline {lstlisting}{\numberline {22}Firewall-Status mit Details anzeigen}{11}{lstlisting.22}\protected@file@percent }
\@writefile{lol}{\contentsline {lstlisting}{\numberline {23}Erwartete Firewall-Ausgabe (gekürzt)}{11}{lstlisting.23}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {2.6}Zusammenfassung}{11}{subsection.2.6}\protected@file@percent }
\BKM@entry{id=21,dest={73756273656374696F6E2E332E31},srcline={10}}{5C3337365C3337375C303030575C303030615C303030735C3030305C3034305C303030695C303030735C303030745C3030305C3034305C303030445C3030306F5C303030635C3030306B5C303030655C303030725C3030305C3034305C303030755C3030306E5C303030645C3030305C3034305C303030775C303030615C303030725C303030755C3030306D5C3030305C3034305C3030306E5C303030755C303030745C3030307A5C303030655C3030306E5C3030305C3034305C303030775C303030695C303030725C3030305C3034305C303030655C303030735C3030303F}
\BKM@entry{id=22,dest={73756273656374696F6E2E332E32},srcline={27}}{5C3337365C3337375C303030445C303030695C303030655C3030305C3034305C303030645C303030725C303030655C303030695C3030305C3034305C303030445C3030306F5C303030635C3030306B5C303030655C303030725C303030665C303030695C3030306C5C303030655C303030735C3030305C3034305C303030695C3030306D5C3030305C3034305C303030445C303030655C303030745C303030615C303030695C3030306C}
\BKM@entry{id=23,dest={73756273756273656374696F6E2E332E322E31},srcline={29}}{5C3337365C3337375C303030425C303030615C303030635C3030306B5C303030655C3030306E5C303030645C3030302D5C303030445C3030306F5C303030635C3030306B5C303030655C303030725C303030665C303030695C3030306C5C303030655C3030303A5C3030305C3034305C303030615C303030705C303030705C303030735C3030302F5C303030615C303030705C303030695C3030302F5C303030445C3030306F5C303030635C3030306B5C303030655C303030725C303030665C303030695C3030306C5C30303065}
\@writefile{toc}{\contentsline {section}{\numberline {3}Docker-Images bauen und App deployen}{12}{section.3}\protected@file@percent }
\newlabel{sec:step03}{{3}{12}{Docker-Images bauen und App deployen}{section.3}{}}
\@writefile{toc}{\contentsline {subsection}{\numberline {3.1}Was ist Docker und warum nutzen wir es?}{12}{subsection.3.1}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {3.2}Die drei Dockerfiles im Detail}{12}{subsection.3.2}\protected@file@percent }
\@writefile{toc}{\contentsline {subsubsection}{\numberline {3.2.1}Backend-Dockerfile: \texttt {apps/api/Dockerfile}}{12}{subsubsection.3.2.1}\protected@file@percent }
\@writefile{lol}{\contentsline {lstlisting}{\numberline {24}Dockerfile für das .NET Backend}{12}{lstlisting.24}\protected@file@percent }
\BKM@entry{id=24,dest={73756273756273656374696F6E2E332E322E32},srcline={64}}{5C3337365C3337375C303030465C303030725C3030306F5C3030306E5C303030745C303030655C3030306E5C303030645C3030302D5C303030445C3030306F5C303030635C3030306B5C303030655C303030725C303030665C303030695C3030306C5C303030655C3030303A5C3030305C3034305C303030615C303030705C303030705C303030735C3030302F5C303030775C303030655C303030625C3030302F5C303030445C3030306F5C303030635C3030306B5C303030655C303030725C303030665C303030695C3030306C5C30303065}
\@writefile{toc}{\contentsline {subsubsection}{\numberline {3.2.2}Frontend-Dockerfile: \texttt {apps/web/Dockerfile}}{13}{subsubsection.3.2.2}\protected@file@percent }
\@writefile{lol}{\contentsline {lstlisting}{\numberline {25}Dockerfile für das React Frontend}{13}{lstlisting.25}\protected@file@percent }
\BKM@entry{id=25,dest={73756273756273656374696F6E2E332E322E33},srcline={99}}{5C3337365C3337375C3030304E5C303030675C303030695C3030306E5C303030785C3030302D5C3030304B5C3030306F5C3030306E5C303030665C303030695C303030675C303030755C303030725C303030615C303030745C303030695C3030306F5C3030306E5C3030303A5C3030305C3034305C303030615C303030705C303030705C303030735C3030302F5C303030775C303030655C303030625C3030302F5C3030306E5C303030675C303030695C3030306E5C303030785C3030302E5C303030635C3030306F5C3030306E5C30303066}
\BKM@entry{id=26,dest={73756273656374696F6E2E332E33},srcline={130}}{5C3337365C3337375C303030445C303030615C303030735C3030305C3034305C303030425C303030615C303030635C3030306B5C303030655C3030306E5C303030645C3030303A5C3030305C3034305C303030505C303030725C3030306F5C303030675C303030725C303030615C3030306D5C3030302E5C303030635C303030735C3030305C3034305C303030695C3030306D5C3030305C3034305C303030445C303030655C303030745C303030615C303030695C3030306C}
\@writefile{toc}{\contentsline {subsubsection}{\numberline {3.2.3}Nginx-Konfiguration: \texttt {apps/web/nginx.conf}}{14}{subsubsection.3.2.3}\protected@file@percent }
\@writefile{lol}{\contentsline {lstlisting}{\numberline {26}Nginx-Konfiguration mit Reverse Proxy}{14}{lstlisting.26}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {3.3}Das Backend: Program.cs im Detail}{15}{subsection.3.3}\protected@file@percent }
\@writefile{lol}{\contentsline {lstlisting}{\numberline {27}Vollständige Program.cs}{15}{lstlisting.27}\protected@file@percent }
\BKM@entry{id=27,dest={73756273656374696F6E2E332E34},srcline={186}}{5C3337365C3337375C303030445C303030655C303030725C3030305C3034305C303030415C303030505C303030495C3030302D5C303030435C3030306C5C303030695C303030655C3030306E5C303030745C3030303A5C3030305C3034305C303030635C3030306C5C303030695C303030655C3030306E5C303030745C3030302E5C303030745C303030735C3030305C3034305C303030695C3030306D5C3030305C3034305C303030445C303030655C303030745C303030615C303030695C3030306C}
\BKM@entry{id=28,dest={73756273656374696F6E2E332E35},srcline={226}}{5C3337365C3337375C303030445C303030615C303030735C3030305C3034305C303030465C303030725C3030306F5C3030306E5C303030745C303030655C3030306E5C303030645C3030303A5C3030305C3034305C303030415C303030705C303030705C3030302E5C303030745C303030735C303030785C3030305C3034305C303030695C3030306D5C3030305C3034305C303030445C303030655C303030745C303030615C303030695C3030306C}
\@writefile{toc}{\contentsline {subsection}{\numberline {3.4}Der API-Client: client.ts im Detail}{16}{subsection.3.4}\protected@file@percent }
\@writefile{lol}{\contentsline {lstlisting}{\numberline {28}Manueller API-Client}{16}{lstlisting.28}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {3.5}Das Frontend: App.tsx im Detail}{16}{subsection.3.5}\protected@file@percent }
\@writefile{lol}{\contentsline {lstlisting}{\numberline {29}Hauptkomponente mit Workout-Liste und Formular}{16}{lstlisting.29}\protected@file@percent }
\BKM@entry{id=29,dest={73756273656374696F6E2E332E36},srcline={265}}{5C3337365C3337375C303030495C3030306D5C303030615C303030675C303030655C303030735C3030305C3034305C303030625C303030615C303030755C303030655C3030306E}
\@writefile{toc}{\contentsline {subsection}{\numberline {3.6}Images bauen}{17}{subsection.3.6}\protected@file@percent }
\BKM@entry{id=30,dest={73756273656374696F6E2E332E37},srcline={287}}{5C3337365C3337375C303030495C3030306D5C303030615C303030675C303030655C303030735C3030305C3034305C303030655C303030785C303030705C3030306F5C303030725C303030745C303030695C303030655C303030725C303030655C3030306E5C3030305C3034305C303030755C3030306E5C303030645C3030305C3034305C303030615C303030755C303030665C3030305C3034305C303030645C303030655C3030306E5C3030305C3034305C303030535C303030655C303030725C303030765C303030655C303030725C3030305C3034305C3030306B5C3030306F5C303030705C303030695C303030655C303030725C303030655C3030306E}
\BKM@entry{id=31,dest={73756273656374696F6E2E332E38},srcline={311}}{5C3337365C3337375C303030435C3030306F5C3030306E5C303030745C303030615C303030695C3030306E5C303030655C303030725C3030305C3034305C303030615C303030755C303030665C3030305C3034305C303030645C303030655C3030306D5C3030305C3034305C303030535C303030655C303030725C303030765C303030655C303030725C3030305C3034305C303030735C303030745C303030615C303030725C303030745C303030655C3030306E}
\@writefile{toc}{\contentsline {subsection}{\numberline {3.7}Images exportieren und auf den Server kopieren}{18}{subsection.3.7}\protected@file@percent }
\@writefile{lol}{\contentsline {lstlisting}{\numberline {30}Images exportieren und kopieren}{18}{lstlisting.30}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {3.8}Container auf dem Server starten}{18}{subsection.3.8}\protected@file@percent }
\@writefile{lol}{\contentsline {lstlisting}{\numberline {31}Docker-Netzwerk, Volume und Container anlegen}{18}{lstlisting.31}\protected@file@percent }
\BKM@entry{id=32,dest={73756273656374696F6E2E332E39},srcline={345}}{5C3337365C3337375C303030415C303030755C303030665C303030675C303030655C303030745C303030725C303030655C303030745C303030655C3030306E5C303030655C3030305C3034305C303030505C303030725C3030306F5C303030625C3030306C5C303030655C3030306D5C303030655C3030305C3034305C303030755C3030306E5C303030645C3030305C3034305C3030304C5C3030305C3336365C303030735C303030755C3030306E5C303030675C303030655C3030306E}
\BKM@entry{id=33,dest={73756273656374696F6E2E332E3130},srcline={372}}{5C3337365C3337375C3030305A5C303030755C303030735C303030615C3030306D5C3030306D5C303030655C3030306E5C303030665C303030615C303030735C303030735C303030755C3030306E5C30303067}
\@writefile{toc}{\contentsline {subsection}{\numberline {3.9}Aufgetretene Probleme und Lösungen}{19}{subsection.3.9}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {3.10}Zusammenfassung}{20}{subsection.3.10}\protected@file@percent }
\global\@namedef{scr@dte@section@lastmaxnumwidth}{11.00392pt}
\global\@namedef{scr@dte@subsection@lastmaxnumwidth}{19.9919pt}
\global\@namedef{scr@dte@subsubsection@lastmaxnumwidth}{28.39188pt}
\global\@namedef{scr@dte@subsection@lastmaxnumwidth}{24.81589pt}
\global\@namedef{scr@dte@subsubsection@lastmaxnumwidth}{28.41586pt}
\@writefile{toc}{\providecommand\tocbasic@end@toc@file{}\tocbasic@end@toc@file}
\gdef \@abspage@last{11}
\gdef \@abspage@last{20}
+11 -6
View File
@@ -1,7 +1,7 @@
# Fdb version 4
["pdflatex"] 1778074526.98351 "/home/computer/projects/fitness-app/LateX/main.tex" "main.pdf" "main" 1778074529.108 0
["pdflatex"] 1778081248.77398 "/home/computer/projects/fitness-app/LateX/main.tex" "main.pdf" "main" 1778081251.03276 0
"/etc/texmf/web2c/texmf.cnf" 1776891072.07073 475 c0e671620eb5563b2130f56340a5fde8 ""
"/home/computer/projects/fitness-app/LateX/main.tex" 1778074523.06229 8392 bdb3148a64fa8b2e535471367f7e4f1e ""
"/home/computer/projects/fitness-app/LateX/main.tex" 1778081236.97865 8413 41aad5c5d32e893cafb487728bf379a6 ""
"/usr/share/texlive/texmf-dist/fonts/enc/dvips/fira/fir_d4q673.enc" 1570828436 2978 6d777d1174162fa94ff58f36782f4570 ""
"/usr/share/texlive/texmf-dist/fonts/enc/dvips/fira/fir_d67aat.enc" 1570828436 3385 21a7e8c8dac3c39de5acda2c56e7bd7e ""
"/usr/share/texlive/texmf-dist/fonts/enc/dvips/fira/fir_iln36p.enc" 1570828436 3071 cfa92ee28d698dd9275559d9d1c3a233 ""
@@ -22,6 +22,8 @@
"/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmsy8.tfm" 1136768653 1120 8b7d695260f3cff42e636090a8002094 ""
"/usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraMono-Bold-tosf-t1--base.tfm" 1556836886 1324 0a7092c8d43950fc7585219358b7afd2 ""
"/usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraMono-Bold-tosf-t1.tfm" 1556836886 1376 464dde77446a9d55bb77e0f8000924a5 ""
"/usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraMono-Oblique-tosf-t1--base.tfm" 1559682857 1544 e1d8938f0433eb7b9914e10941e55e1c ""
"/usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraMono-Oblique-tosf-t1.tfm" 1559682857 1596 ad99e5db8e2fbbe207fd41ae14f51134 ""
"/usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraMono-Regular-tosf-t1--base.tfm" 1556836886 1308 9050f92394f218407f72f188eb4d1a33 ""
"/usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraMono-Regular-tosf-t1.tfm" 1556836886 1360 dfbacd55e2269d2ff422e3de3884feab ""
"/usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraSans-Bold-osf-t1--base.tfm" 1558644978 1796 11e21ab836516246ef24a10a351d2541 ""
@@ -32,10 +34,12 @@
"/usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraSans-Regular-osf-ts1.tfm" 1554498137 1456 8ee3e51b7984f3df81900715b34ebdeb ""
"/usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy10.pfb" 1248133631 32569 5e5ddc8df908dea60932f3c484a54c0d ""
"/usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraMono-Bold.pfb" 1559682857 205973 90710d15f8d1d4d4f6bdd622521b398f ""
"/usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraMono-Oblique.pfb" 1559682857 271356 1b50b6cf73a83edb93bda1aa45a1679d ""
"/usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraMono-Regular.pfb" 1559682857 192962 8eef27b162fb2864c33843b9ad6cdbc2 ""
"/usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraSans-Bold.pfb" 1554498137 485112 0bd86946afd04160ee1229d86cd76902 ""
"/usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraSans-Regular.pfb" 1554498137 457228 86b22575fa05027949aa646d89908746 ""
"/usr/share/texlive/texmf-dist/fonts/vf/public/fira/FiraMono-Bold-tosf-t1.vf" 1556836886 1692 8b219f0f8fd0e6dfdce79d58d9f68088 ""
"/usr/share/texlive/texmf-dist/fonts/vf/public/fira/FiraMono-Oblique-tosf-t1.vf" 1559682857 1696 f96e3873fa4905e557b6ed829aef1f0f ""
"/usr/share/texlive/texmf-dist/fonts/vf/public/fira/FiraMono-Regular-tosf-t1.vf" 1556836886 1696 af65436ccdde59c952e95f8cc457dbb5 ""
"/usr/share/texlive/texmf-dist/fonts/vf/public/fira/FiraSans-Bold-osf-t1.vf" 1558644978 1724 1ac6e230aa57cf8ba8f965fbceffd874 ""
"/usr/share/texlive/texmf-dist/fonts/vf/public/fira/FiraSans-Regular-osf-t1.vf" 1558644978 1724 e5cbcafbef1b8b346466340ef078339c ""
@@ -130,12 +134,13 @@
"/usr/share/texmf/web2c/texmf.cnf" 1707919699 40399 f2c302f7d2af602abb742093540a5834 ""
"/var/lib/texmf/fonts/map/pdftex/updmap/pdftex.map" 1776891108.46284 5472669 54eaf61a88b6b7896ebd0dac973cb29c ""
"/var/lib/texmf/web2c/pdftex/pdflatex.fmt" 1776891271 8211336 7fc26d317f030a4855527787ba3b41d3 ""
"main.aux" 1778074529.0082 14507 a445efc066d6f1fab05528a5ef320e35 "pdflatex"
"main.out" 1778074528.61242 0 d41d8cd98f00b204e9800998ecf8427e "pdflatex"
"main.tex" 1778074523.06229 8392 bdb3148a64fa8b2e535471367f7e4f1e ""
"main.toc" 1778074529.0122 1951 c589379c8507d6fcc5ff4c6e2179786a "pdflatex"
"main.aux" 1778081250.91843 23846 804a4d87b04ab7403c0abdcb70108b86 "pdflatex"
"main.out" 1778081250.30135 0 d41d8cd98f00b204e9800998ecf8427e "pdflatex"
"main.tex" 1778081236.97865 8413 41aad5c5d32e893cafb487728bf379a6 ""
"main.toc" 1778081250.92443 3404 2187def0eadc57fc64da16e547916901 "pdflatex"
"step_01.tex" 1778074119.30934 10807 dd7fc11a20ecebed2f07638ceddcf838 ""
"step_02.tex" 1778074524.52004 9161 30219f0c68c4ae118067f27c09a123fb ""
"step_03.tex" 1778081246.2535 17575 0dde7ed301abaec40a3b2d19357b160b ""
(generated)
"main.aux"
"main.log"
+17 -7
View File
@@ -230,11 +230,18 @@ INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmsy8.tfm
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmsy6.tfm
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex8.tfm
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex7.tfm
INPUT /usr/share/texlive/texmf-dist/tex/latex/fira/T1FiraMono-TOsF.fd
INPUT /usr/share/texlive/texmf-dist/tex/latex/fira/T1FiraMono-TOsF.fd
INPUT /usr/share/texlive/texmf-dist/tex/latex/fira/T1FiraMono-TOsF.fd
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraMono-Regular-tosf-t1.tfm
OUTPUT main.toc
INPUT /usr/share/texlive/texmf-dist/fonts/vf/public/fira/FiraSans-Bold-osf-t1.vf
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraSans-Bold-osf-t1--base.tfm
INPUT /usr/share/texlive/texmf-dist/fonts/vf/public/fira/FiraSans-Regular-osf-t1.vf
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraSans-Regular-osf-t1--base.tfm
INPUT /usr/share/texlive/texmf-dist/fonts/vf/public/fira/FiraMono-Regular-tosf-t1.vf
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraMono-Regular-tosf-t1--base.tfm
INPUT /usr/share/texlive/texmf-dist/fonts/enc/dvips/fira/fir_d4q673.enc
INPUT ./step_01.tex
INPUT ./step_01.tex
INPUT step_01.tex
@@ -242,18 +249,11 @@ INPUT /usr/share/texlive/texmf-dist/tex/latex/fira/TS1FiraSans-OsF.fd
INPUT /usr/share/texlive/texmf-dist/tex/latex/fira/TS1FiraSans-OsF.fd
INPUT /usr/share/texlive/texmf-dist/tex/latex/fira/TS1FiraSans-OsF.fd
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraSans-Regular-osf-ts1.tfm
INPUT /usr/share/texlive/texmf-dist/tex/latex/fira/T1FiraMono-TOsF.fd
INPUT /usr/share/texlive/texmf-dist/tex/latex/fira/T1FiraMono-TOsF.fd
INPUT /usr/share/texlive/texmf-dist/tex/latex/fira/T1FiraMono-TOsF.fd
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraMono-Regular-tosf-t1.tfm
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraMono-Regular-tosf-t1.tfm
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraSans-Regular-osf-t1.tfm
INPUT /usr/share/texlive/texmf-dist/fonts/vf/public/fira/FiraSans-Regular-osf-ts1.vf
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraSans-Regular-osf-ts1--base.tfm
INPUT /usr/share/texlive/texmf-dist/fonts/enc/dvips/fira/fir_d67aat.enc
INPUT /usr/share/texlive/texmf-dist/fonts/vf/public/fira/FiraMono-Regular-tosf-t1.vf
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraMono-Regular-tosf-t1--base.tfm
INPUT /usr/share/texlive/texmf-dist/fonts/enc/dvips/fira/fir_d4q673.enc
INPUT /usr/share/texlive/texmf-dist/fonts/vf/public/fira/FiraSans-Regular-osf-t1.vf
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraSans-Regular-osf-t1--base.tfm
INPUT /usr/share/texlive/texmf-dist/fonts/vf/public/fira/FiraMono-Regular-tosf-t1.vf
@@ -264,10 +264,20 @@ INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraMono-Bold-tosf-t1-
INPUT ./step_02.tex
INPUT ./step_02.tex
INPUT step_02.tex
INPUT ./step_03.tex
INPUT ./step_03.tex
INPUT step_03.tex
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraMono-Bold-tosf-t1.tfm
INPUT /usr/share/texlive/texmf-dist/fonts/vf/public/fira/FiraMono-Bold-tosf-t1.vf
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraMono-Bold-tosf-t1--base.tfm
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraMono-Oblique-tosf-t1.tfm
INPUT /usr/share/texlive/texmf-dist/fonts/vf/public/fira/FiraMono-Oblique-tosf-t1.vf
INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/fira/FiraMono-Oblique-tosf-t1--base.tfm
INPUT main.aux
INPUT ./main.out
INPUT ./main.out
INPUT /usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraMono-Bold.pfb
INPUT /usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraMono-Oblique.pfb
INPUT /usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraMono-Regular.pfb
INPUT /usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraSans-Bold.pfb
INPUT /usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraSans-Regular.pfb
+67 -29
View File
@@ -1,4 +1,4 @@
This is pdfTeX, Version 3.141592653-2.6-1.40.25 (TeX Live 2023/Debian) (preloaded format=pdflatex 2026.4.22) 6 MAY 2026 15:35
This is pdfTeX, Version 3.141592653-2.6-1.40.25 (TeX Live 2023/Debian) (preloaded format=pdflatex 2026.4.22) 6 MAY 2026 17:27
entering extended mode
restricted \write18 enabled.
file:line:error style messages enabled.
@@ -615,13 +615,26 @@ File: mt-cmr.cfg 2013/05/19 v2.2 microtype config. file: Computer Modern Roman (
{/var/lib/texmf/fonts/map/pdftex/updmap/pdftex.map}{/usr/share/texlive/texmf-dist/fonts/enc/dvips/fira/fir_iln36p.enc}]
Package tocbasic Info: character protrusion at toc deactivated on input line 239.
(./main.toc)
(./main.toc
LaTeX Font Info: Trying to load font information for T1+FiraMono-TOsF on input line 24.
(/usr/share/texlive/texmf-dist/tex/latex/fira/T1FiraMono-TOsF.fd
File: T1FiraMono-TOsF.fd 2019/10/10 (autoinst) Font definitions for T1/FiraMono-TOsF.
)
LaTeX Font Info: Font shape `T1/FiraMono-TOsF/m/n' in size <12> not available
(Font) Font shape `T1/FiraMono-TOsF/regular/n' tried instead on input line 24.
LaTeX Font Info: Font shape `T1/FiraMono-TOsF/regular/n' will be
(Font) scaled to size 10.79993pt on input line 24.
Package microtype Info: Loading generic protrusion settings for font family
(microtype) `FiraMono-TOsF' (encoding: T1).
(microtype) For optimal results, create family-specific settings.
(microtype) See the microtype manual for details.
)
\tf@toc=\write4
\openout4 = `main.toc'.
[2
] (./step_01.tex
{/usr/share/texlive/texmf-dist/fonts/enc/dvips/fira/fir_d4q673.enc}] (./step_01.tex
LaTeX Font Info: Trying to load font information for TS1+FiraSans-OsF on input line 21.
(/usr/share/texlive/texmf-dist/tex/latex/fira/TS1FiraSans-OsF.fd
File: TS1FiraSans-OsF.fd 2019/10/10 (autoinst) Font definitions for TS1/FiraSans-OsF.
@@ -634,18 +647,6 @@ Package microtype Info: Loading generic protrusion settings for font family
(microtype) `FiraSans-OsF' (encoding: TS1).
(microtype) For optimal results, create family-specific settings.
(microtype) See the microtype manual for details.
LaTeX Font Info: Trying to load font information for T1+FiraMono-TOsF on input line 21.
(/usr/share/texlive/texmf-dist/tex/latex/fira/T1FiraMono-TOsF.fd
File: T1FiraMono-TOsF.fd 2019/10/10 (autoinst) Font definitions for T1/FiraMono-TOsF.
)
LaTeX Font Info: Font shape `T1/FiraMono-TOsF/m/n' in size <12> not available
(Font) Font shape `T1/FiraMono-TOsF/regular/n' tried instead on input line 21.
LaTeX Font Info: Font shape `T1/FiraMono-TOsF/regular/n' will be
(Font) scaled to size 10.79993pt on input line 21.
Package microtype Info: Loading generic protrusion settings for font family
(microtype) `FiraMono-TOsF' (encoding: T1).
(microtype) For optimal results, create family-specific settings.
(microtype) See the microtype manual for details.
Package hyperref Info: bookmark level for unknown lstlisting defaults to 0 on input line 35.
LaTeX Font Info: Font shape `T1/FiraMono-TOsF/m/n' in size <10> not available
(Font) Font shape `T1/FiraMono-TOsF/regular/n' tried instead on input line 35.
@@ -657,7 +658,7 @@ LaTeX Font Info: Font shape `T1/FiraSans-OsF/regular/n' will be
(Font) scaled to size 10.0pt on input line 36.
[3
{/usr/share/texlive/texmf-dist/fonts/enc/dvips/fira/fir_d67aat.enc}{/usr/share/texlive/texmf-dist/fonts/enc/dvips/fira/fir_d4q673.enc}] [4]
{/usr/share/texlive/texmf-dist/fonts/enc/dvips/fira/fir_d67aat.enc}] [4]
LaTeX Font Info: Font shape `T1/FiraMono-TOsF/b/n' in size <10> not available
(Font) Font shape `T1/FiraMono-TOsF/bold/n' tried instead on input line 140.
LaTeX Font Info: Font shape `T1/FiraMono-TOsF/bold/n' will be
@@ -709,7 +710,44 @@ Overfull \hbox (3.891pt too wide) in paragraph at lines 77--78
[]\T1/FiraSans-OsF/regular/n/12 (-20) HTTP-Verbindungen kön-nen von An-grei-fern ver-än-dert wer-den (Man-in-the-Middle)
[]
[9] [10]) [11] (./main.aux)
[9] [10]) (./step_03.tex [11]
LaTeX Font Info: Font shape `T1/FiraMono-TOsF/b/n' in size <12> not available
(Font) Font shape `T1/FiraMono-TOsF/bold/n' tried instead on input line 29.
LaTeX Font Info: Font shape `T1/FiraMono-TOsF/bold/n' will be
(Font) scaled to size 10.79993pt on input line 29.
[12] [13]
Overfull \hbox (74.52446pt too wide) in paragraph at lines 125--126
[]\T1/FiraMono-TOsF/regular/n/12 location / \T1/FiraSans-OsF/regular/n/12 (-20) ^^U An-fra-gen an die Haupt-sei-te $\OMS/cmsy/m/n/12 !$ \T1/FiraSans-OsF/regular/n/12 (-20) lie-fert React-Dateien aus \T1/FiraMono-TOsF/regular/n/12 /usr/share/nginx/html
[]
Overfull \hbox (36.2292pt too wide) in paragraph at lines 126--127
[]\T1/FiraMono-TOsF/regular/n/12 location /api/ \T1/FiraSans-OsF/regular/n/12 (-20) ^^U An-fra-gen an \T1/FiraMono-TOsF/regular/n/12 /api/* $\OMS/cmsy/m/n/12 !$ \T1/FiraSans-OsF/regular/n/12 (-20) lei-tet sie an das Ba-ckend (\T1/FiraMono-TOsF/regular/n/12 fitness-api:5000\T1/FiraSans-OsF/regular/n/12 (-20) )
[]
[14] [15]
LaTeX Font Info: Font shape `T1/FiraMono-TOsF/m/it' in size <10> not available
(Font) Font shape `T1/FiraMono-TOsF/regular/it' tried instead on input line 214.
LaTeX Font Info: Font shape `T1/FiraMono-TOsF/regular/it' in size <10> not available
(Font) Font shape `T1/FiraMono-TOsF/regular/sl' tried instead on input line 214.
LaTeX Font Info: Font shape `T1/FiraMono-TOsF/regular/sl' will be
(Font) scaled to size 8.99994pt on input line 214.
Overfull \hbox (5.87708pt too wide) in paragraph at lines 220--221
[]\T1/FiraMono-TOsF/regular/n/12 API_BASE = \T1/FiraSans-OsF/regular/n/12 (-20) ^^U Kei-ne ab-so-lu-te URL! Statt-des-sen re-la-ti-ve Pfa-de wie \T1/FiraMono-TOsF/regular/n/12 /api/workouts\T1/FiraSans-OsF/regular/n/12 (-20) .
[]
[16] [17] [18]
Overfull \hbox (10.69511pt too wide) in paragraph at lines 341--342
[]\T1/FiraMono-TOsF/regular/n/12 -v fitness-data:/app/data \T1/FiraSans-OsF/regular/n/12 (-20) ^^U Bin-det das Vo-lu-me \T1/FiraMono-TOsF/regular/n/12 fitness-data \T1/FiraSans-OsF/regular/n/12 (-20) in den Container-
[]
Overfull \hbox (0.99844pt too wide) in paragraph at lines 361--362
[]\T1/FiraSans-OsF/regular/n/12 (-20) Ursache: Im Cli-ent stand \T1/FiraMono-TOsF/regular/n/12 const API_BASE = "http://192.168.178.189:5107"\T1/FiraSans-OsF/regular/n/12 (-20) .
[]
[19]) [20] (./main.aux)
***********
LaTeX2e <2023-11-01> patch level 1
L3 programming layer <2024-01-22>
@@ -718,18 +756,18 @@ Package rerunfilecheck Info: File `main.out' has not changed.
(rerunfilecheck) Checksum: D41D8CD98F00B204E9800998ECF8427E;0.
)
Here is how much of TeX's memory you used:
19308 strings out of 474222
335876 string characters out of 5748733
2134975 words of memory out of 5000000
41036 multiletter control sequences out of 15000+600000
686653 words of font info for 181 fonts, out of 8000000 for 9000
19894 strings out of 474222
343688 string characters out of 5748733
2200975 words of memory out of 5000000
41353 multiletter control sequences out of 15000+600000
692737 words of font info for 213 fonts, out of 8000000 for 9000
1141 hyphenation exceptions out of 8191
108i,10n,107p,10941b,2145s stack positions out of 10000i,1000n,20000p,200000b,200000s
</usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraMono-Bold.pfb></usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraMono-Regular.pfb></usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraSans-Bold.pfb></usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraSans-Regular.pfb></usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy10.pfb>
Output written on main.pdf (11 pages, 219584 bytes).
108i,10n,107p,10941b,2229s stack positions out of 10000i,1000n,20000p,200000b,200000s
</usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraMono-Bold.pfb></usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraMono-Oblique.pfb></usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraMono-Regular.pfb></usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraSans-Bold.pfb></usr/share/texlive/texmf-dist/fonts/type1/public/fira/FiraSans-Regular.pfb></usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy10.pfb>
Output written on main.pdf (20 pages, 300891 bytes).
PDF statistics:
292 PDF objects out of 1000 (max. 8388607)
265 compressed objects within 3 object streams
115 named destinations out of 1000 (max. 500000)
44697 words of extra memory for PDF output out of 51595 (max. 10000000)
651 PDF objects out of 1000 (max. 8388607)
609 compressed objects within 7 object streams
336 named destinations out of 1000 (max. 500000)
45833 words of extra memory for PDF output out of 51595 (max. 10000000)
BIN
View File
Binary file not shown.
Binary file not shown.
+1
View File
@@ -245,6 +245,7 @@
% ============================================
\input{step_01.tex}
\input{step_02.tex}
\input{step_03.tex}
% Weitere Kapitel folgen hier:
% \input{step_02.tex}
+14
View File
@@ -18,4 +18,18 @@
\contentsline {subsubsection}{\numberline {2.5.3}Firewall aktivieren}{10}{subsubsection.2.5.3}%
\contentsline {subsubsection}{\numberline {2.5.4}Konfiguration überprüfen}{11}{subsubsection.2.5.4}%
\contentsline {subsection}{\numberline {2.6}Zusammenfassung}{11}{subsection.2.6}%
\contentsline {section}{\numberline {3}Docker-Images bauen und App deployen}{12}{section.3}%
\contentsline {subsection}{\numberline {3.1}Was ist Docker und warum nutzen wir es?}{12}{subsection.3.1}%
\contentsline {subsection}{\numberline {3.2}Die drei Dockerfiles im Detail}{12}{subsection.3.2}%
\contentsline {subsubsection}{\numberline {3.2.1}Backend-Dockerfile: \texttt {apps/api/Dockerfile}}{12}{subsubsection.3.2.1}%
\contentsline {subsubsection}{\numberline {3.2.2}Frontend-Dockerfile: \texttt {apps/web/Dockerfile}}{13}{subsubsection.3.2.2}%
\contentsline {subsubsection}{\numberline {3.2.3}Nginx-Konfiguration: \texttt {apps/web/nginx.conf}}{14}{subsubsection.3.2.3}%
\contentsline {subsection}{\numberline {3.3}Das Backend: Program.cs im Detail}{15}{subsection.3.3}%
\contentsline {subsection}{\numberline {3.4}Der API-Client: client.ts im Detail}{16}{subsection.3.4}%
\contentsline {subsection}{\numberline {3.5}Das Frontend: App.tsx im Detail}{16}{subsection.3.5}%
\contentsline {subsection}{\numberline {3.6}Images bauen}{17}{subsection.3.6}%
\contentsline {subsection}{\numberline {3.7}Images exportieren und auf den Server kopieren}{18}{subsection.3.7}%
\contentsline {subsection}{\numberline {3.8}Container auf dem Server starten}{18}{subsection.3.8}%
\contentsline {subsection}{\numberline {3.9}Aufgetretene Probleme und Lösungen}{19}{subsection.3.9}%
\contentsline {subsection}{\numberline {3.10}Zusammenfassung}{20}{subsection.3.10}%
\providecommand \tocbasic@end@toc@file {}\tocbasic@end@toc@file
+382
View File
@@ -0,0 +1,382 @@
% ============================================
% STEP 03: DOCKER, DEPLOYMENT & ERSTE LIVE-APP
% ============================================
\section{Docker-Images bauen und App deployen}
\label{sec:step03}
In diesem Schritt bringen wir unsere Fitness-App vom lokalen Entwicklungsrechner auf den Server und machen sie weltweit erreichbar. Dafür nutzen wir \textbf{Docker} eine Container-Plattform, die Anwendungen in standardisierten, isolierten Umgebungen verpackt und ausführt.
\subsection{Was ist Docker und warum nutzen wir es?}
Docker funktioniert wie ein \textbf{Versandkarton für Software}. Stell dir vor, du verschickst ein zerbrechliches Paket: Du packst es in einen genormten Karton, der überall auf der Welt gleich behandelt wird egal ob in Deutschland, Japan oder Brasilien. Docker macht dasselbe mit Software:
\begin{itemize}
\item \textbf{Image} = Der Bauplan des Kartons (inkl. Inhalt). Ein Image enthält das Betriebssystem, alle Abhängigkeiten und die Anwendung selbst.
\item \textbf{Container} = Der tatsächliche laufende Karton. Ein Container ist eine laufende Instanz eines Images.
\item \textbf{Volume} = Ein separater Speicherort, der den Container überlebt. Wie ein externer USB-Stick, den man an den Karton anschließt.
\end{itemize}
\textbf{Konkret für unser Projekt:}
\begin{itemize}
\item \texttt{fitness-api:latest} Image mit .NET 8 Backend
\item \texttt{fitness-web:latest} Image mit React Frontend + Nginx
\item \texttt{fitness-data} Volume für die SQLite-Datenbank (überlebt Container-Neustarts)
\end{itemize}
\subsection{Die drei Dockerfiles im Detail}
\subsubsection{Backend-Dockerfile: \texttt{apps/api/Dockerfile}}
\begin{lstlisting}[language=Dockerfile, caption={Dockerfile für das .NET Backend}]
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["apps/api/Api.csproj", "apps/api/"]
RUN dotnet restore "apps/api/Api.csproj"
COPY . .
WORKDIR /src/apps/api
RUN dotnet publish "Api.csproj" -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 5000
ENV ASPNETCORE_URLS=http://+:5000
ENTRYPOINT ["dotnet", "Api.dll"]
\end{lstlisting}
\textbf{Zeile für Zeile erklärt:}
\begin{enumerate}
\item \texttt{FROM ... AS build} Startet mit dem .NET 8 SDK Image (enthält Compiler, Tools). Der Alias \texttt{build} erlaubt später darauf zuzugreifen.
\item \texttt{WORKDIR /src} Setzt das Arbeitsverzeichnis im Container auf \texttt{/src}.
\item \texttt{COPY ["apps/api/Api.csproj", "apps/api/"]} Kopiert NUR die Projektdatei. Dadurch cached Docker diesen Schritt: Solange sich \texttt{Api.csproj} nicht ändert, wird der Cache verwendet $\rightarrow$ schnellere Builds!
\item \texttt{RUN dotnet restore} Lädt alle NuGet-Pakete herunter (Entity Framework, NSwag, Swagger usw.).
\item \texttt{COPY . .} Kopiert den gesamten restlichen Quellcode.
\item \texttt{WORKDIR /src/apps/api} Wechselt ins Backend-Verzeichnis.
\item \texttt{RUN dotnet publish} Kompiliert die Anwendung im Release-Modus in den Ordner \texttt{/app/publish}.
\item \texttt{FROM ... AS final} Startet ein NEUES, schlankeres Image (nur ASP.NET Runtime, kein SDK). Das spart Speicher!
\item \texttt{COPY --from=build ...} Kopiert die kompilierte Anwendung aus dem Build-Image.
\item \texttt{EXPOSE 5000} Dokumentiert, dass der Container auf Port 5000 lauscht.
\item \texttt{ENV ASPNETCORE\_URLS=http://+:5000} Sagt .NET, es soll auf Port 5000 auf ALLEN Netzwerkschnittstellen lauschen.
\item \texttt{ENTRYPOINT ["dotnet", "Api.dll"]} Startet die Anwendung beim Container-Start.
\end{enumerate}
\subsubsection{Frontend-Dockerfile: \texttt{apps/web/Dockerfile}}
\begin{lstlisting}[language=Dockerfile, caption={Dockerfile für das React Frontend}]
FROM node:22-alpine AS build
WORKDIR /app
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY apps/web/package.json apps/web/
RUN npm install -g pnpm && pnpm install --no-frozen-lockfile
COPY apps/web/ apps/web/
WORKDIR /app/apps/web
RUN pnpm run build
FROM nginx:stable-alpine
COPY --from=build /app/apps/web/dist /usr/share/nginx/html
COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
\end{lstlisting}
\textbf{Zeile für Zeile erklärt:}
\begin{enumerate}
\item \texttt{FROM node:22-alpine} Leichtgewichtiges Node.js 22 Image (Alpine Linux = nur $\sim$5 MB statt $\sim$180 MB bei Ubuntu).
\item \texttt{COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./} Kopiert die Monorepo-Konfiguration aus dem Root. \texttt{pnpm-workspace.yaml} ist nötig, damit pnpm die Workspace-Struktur erkennt.
\item \texttt{COPY apps/web/package.json apps/web/} Kopiert die Frontend-Paketliste an ihren Platz.
\item \texttt{RUN npm install -g pnpm \&\& pnpm install} Installiert pnpm global und dann alle Abhängigkeiten.
\item \texttt{COPY apps/web/ apps/web/} Kopiert den restlichen Frontend-Code.
\item \texttt{WORKDIR /app/apps/web} Wechselt ins Frontend-Verzeichnis.
\item \texttt{RUN pnpm run build} Baut das Frontend mit Vite (erzeugt \texttt{dist/}).
\item \texttt{FROM nginx:stable-alpine} NEUES schlankes Image mit Nginx (Webserver).
\item \texttt{COPY --from=build ... /usr/share/nginx/html} Kopiert den Build-Output in Nginx's Standard-Webverzeichnis.
\item \texttt{COPY apps/web/nginx.conf ...} Unsere eigene Nginx-Konfiguration.
\item \texttt{EXPOSE 80} HTTP-Port.
\item \texttt{CMD ["nginx", "-g", "daemon off;"]} Startet Nginx im Vordergrund (Container bleibt am Leben).
\end{enumerate}
\subsubsection{Nginx-Konfiguration: \texttt{apps/web/nginx.conf}}
\begin{lstlisting}[language=Bash, caption={Nginx-Konfiguration mit Reverse Proxy}]
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://fitness-api:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
\end{lstlisting}
\textbf{Erklärung:} Dies ist ein \textbf{Reverse Proxy}. Nginx nimmt alle Anfragen entgegen und entscheidet, wohin sie weitergeleitet werden:
\begin{itemize}
\item \texttt{location /} Anfragen an die Hauptseite $\rightarrow$ liefert React-Dateien aus \texttt{/usr/share/nginx/html}
\item \texttt{location /api/} Anfragen an \texttt{/api/*} $\rightarrow$ leitet sie an das Backend (\texttt{fitness-api:5000}) weiter
\item \texttt{try\_files \$uri \$uri/ /index.html} Sorgt dafür, dass Reacts Client-Side-Routing funktioniert (z.B. \texttt{/workouts/123} wird an React weitergegeben, nicht als 404 beantwortet)
\end{itemize}
\subsection{Das Backend: Program.cs im Detail}
\begin{lstlisting}[language=CSharp, caption={Vollständige Program.cs}]
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
var dbPath = Path.Combine("/app/data", "fitness.db");
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite($"Data Source={dbPath}"));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument();
builder.Services.AddSwaggerGen();
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
db.Database.EnsureCreated();
}
if (app.Environment.IsDevelopment())
{
app.UseOpenApi();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.MapPost("/api/workouts", async (Workout workout, AppDbContext db) => { ... });
app.MapGet("/api/workouts", async (AppDbContext db) => ...);
app.MapGet("/api/workouts/{id:guid}", async (Guid id, AppDbContext db) => ...);
app.MapPut("/api/workouts/{id:guid}", async (Guid id, Workout input, AppDbContext db) => ...);
app.MapDelete("/api/workouts/{id:guid}", async (Guid id, AppDbContext db) => ...);
app.MapGet("/", () => "H Fitness API läuft!");
app.Run();
\end{lstlisting}
\textbf{Die NuGet-Pakete in der .csproj-Datei:}
\begin{itemize}
\item \texttt{Microsoft.EntityFrameworkCore.Sqlite} (8.0.14) EF Core für SQLite
\item \texttt{Microsoft.EntityFrameworkCore.Design} (8.0.14) EF Core Tools für Migrationen
\item \texttt{NSwag.AspNetCore} (14.1.0) OpenAPI/Swagger-Generator
\item \texttt{Swashbuckle.AspNetCore} (6.6.2) Swagger UI (die schöne Oberfläche)
\end{itemize}
\textbf{Wichtige Details:}
\begin{itemize}
\item \texttt{Path.Combine("/app/data", "fitness.db")} Im Docker-Container liegt die Datenbank im Volume-Ordner \texttt{/app/data}. Das stellt sicher, dass die Daten erhalten bleiben, auch wenn der Container gelöscht und neu erstellt wird.
\item \texttt{db.Database.EnsureCreated()} Erstellt die Datenbank-Tabellen automatisch beim Start, falls sie noch nicht existieren. Erspart uns manuelle Migrationen im Produktivbetrieb.
\item Die CRUD-Endpunkte sind \textbf{Minimal API Endpoints} .NET 8's leichtgewichtige Alternative zu Controllern.
\end{itemize}
\subsection{Der API-Client: client.ts im Detail}
\begin{lstlisting}[language=TypeScript, caption={Manueller API-Client}]
const API_BASE = "";
export interface Workout {
id?: string;
name: string;
date: string;
durationMinutes: number;
notes?: string;
}
export const fitnessApi = {
async getWorkouts(): Promise<Workout[]> {
const res = await fetch(`/api/workouts`);
return res.json();
},
async createWorkout(workout: Workout): Promise<Workout> {
const res = await fetch(`/api/workouts`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(workout),
});
return res.json();
},
// Weitere Methoden: getWorkout, updateWorkout, deleteWorkout...
};
\end{lstlisting}
\textbf{Erklärung:}
\begin{itemize}
\item \texttt{API\_BASE = ""} Keine absolute URL! Stattdessen relative Pfade wie \texttt{/api/workouts}. Der Browser sendet die Anfrage dann an dieselbe Domain, auf der die Seite gehostet ist. Nginx leitet sie an das Backend weiter.
\item \texttt{interface Workout} TypeScript-Interface für Typsicherheit. Stellt sicher, dass wir keine falschen Felder an die API senden.
\item \texttt{fetch()} Native Browser-API für HTTP-Anfragen. Kein Axios, kein jQuery nötig!
\item Die Methoden geben direkt das geparste JSON zurück.
\end{itemize}
\subsection{Das Frontend: App.tsx im Detail}
\begin{lstlisting}[language=TypeScript, caption={Hauptkomponente mit Workout-Liste und Formular}]
function App() {
const [workouts, setWorkouts] = useState<Workout[]>([]);
const [form, setForm] = useState<Workout>({ ... });
const loadWorkouts = async () => {
const data = await fitnessApi.getWorkouts();
setWorkouts(data);
};
useEffect(() => { loadWorkouts(); }, []);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await fitnessApi.createWorkout({ ...form });
loadWorkouts();
};
return (
<div className="min-h-screen bg-gray-950 text-white p-4 max-w-md mx-auto">
{/* Formular */}
<form onSubmit={handleSubmit}>...</form>
{/* Workout-Liste */}
{workouts.map(w => ( ... ))}
</div>
);
}
\end{lstlisting}
\textbf{Erklärung:}
\begin{itemize}
\item \texttt{useState} Reacts State-Management für die Workout-Liste und das Formular.
\item \texttt{useEffect} Lädt die Workouts einmalig beim ersten Rendern der Komponente.
\item \texttt{handleSubmit} Wird beim Absenden des Formulars aufgerufen. Verhindert den Standard-Seiten-Reload (\texttt{e.preventDefault()}), sendet das Workout an die API und lädt die Liste neu.
\item Tailwind CSS-Klassen wie \texttt{bg-gray-950}, \texttt{text-white}, \texttt{p-4} gestalten die App im Dark-Mode.
\end{itemize}
\subsection{Images bauen}
Am Anfang war das Frontend-Image fehlerhaft. Hier die drei wichtigsten Fixes:
\textbf{Fehler 1: \texttt{pnpm-lock.yaml} nicht gefunden}
\begin{itemize}
\item Ursache: Dockerfile suchte in \texttt{apps/web/}, aber die Datei liegt im Root.
\item Lösung: \texttt{COPY pnpm-lock.yaml ./} (vom Root kopieren).
\end{itemize}
\textbf{Fehler 2: \texttt{--frozen-lockfile} schlug fehl}
\begin{itemize}
\item Ursache: pnpm-Lockfile war nicht aktuell mit der Root-\texttt{package.json}.
\item Lösung: \texttt{--no-frozen-lockfile} verwenden, damit pnpm fehlende Pakete nachinstalliert.
\end{itemize}
\textbf{Fehler 3: TypeScript Compiler (\texttt{tsc}) nicht gefunden}
\begin{itemize}
\item Ursache: \texttt{tsc -b} benötigt TypeScript als Abhängigkeit, die im Container fehlte.
\item Lösung: Build-Script von \texttt{"tsc -b \&\& vite build"} auf \texttt{"vite build"} geändert. Vite führt den TypeScript-Check beim Dev-Server durch im Produktions-Build reicht die reine Vite-Kompilierung.
\end{itemize}
\subsection{Images exportieren und auf den Server kopieren}
\begin{lstlisting}[language=Bash, caption={Images exportieren und kopieren}]
# Images als tar-Datei speichern
docker save fitness-api:latest fitness-web:latest -o fitness-images.tar
# Auf den Server kopieren (scp = Secure Copy über SSH)
scp fitness-images.tar testserver:/root/
# Auf dem Server importieren
ssh testserver
docker load -i /root/fitness-images.tar
docker images | grep fitness
\end{lstlisting}
\textbf{Befehle erklärt:}
\begin{itemize}
\item \texttt{docker save} Exportiert Docker-Images in eine portable tar-Datei.
\item \texttt{scp} Secure Copy: Kopiert Dateien verschlüsselt über SSH.
\item \texttt{testserver:/root/} Der Alias aus unserer \texttt{\textasciitilde/.ssh/config}. Die Datei landet im \texttt{/root/}-Verzeichnis des Servers.
\item \texttt{docker load -i} Importiert Images aus einer tar-Datei in Docker.
\item \texttt{docker images} Listet alle lokal verfügbaren Docker-Images auf.
\end{itemize}
\subsection{Container auf dem Server starten}
\begin{lstlisting}[language=Bash, caption={Docker-Netzwerk, Volume und Container anlegen}]
# Volume für die Datenbank (überlebt Container-Neustarts)
docker volume create fitness-data
# Netzwerk (damit Backend und Frontend kommunizieren können)
docker network create fitness-net
# Backend starten
docker run -d \
--name fitness-api \
--network fitness-net \
-v fitness-data:/app/data \
-p 5000:5000 \
fitness-api:latest
# Frontend starten
docker run -d \
--name fitness-web \
--network fitness-net \
-p 80:80 \
fitness-web:latest
\end{lstlisting}
\textbf{Optionen erklärt:}
\begin{itemize}
\item \texttt{-d} Detached Mode: Container läuft im Hintergrund (gibt die Konsole frei).
\item \texttt{--name fitness-api} Gibt dem Container einen festen Namen (sonst vergibt Docker Zufallsnamen).
\item \texttt{--network fitness-net} Bindet den Container in unser Docker-Netzwerk ein. Container im selben Netzwerk können sich über ihren Namen erreichen (z.B. \texttt{fitness-api}).
\item \texttt{-v fitness-data:/app/data} Bindet das Volume \texttt{fitness-data} in den Container-Pfad \texttt{/app/data} ein. Alles, was im Container unter \texttt{/app/data} gespeichert wird, landet tatsächlich im Volume und überlebt.
\item \texttt{-p 5000:5000} Port-Mapping: Leitet Port 5000 des Hosts (Server) an Port 5000 des Containers weiter.
\end{itemize}
\subsection{Aufgetretene Probleme und Lösungen}
\textbf{Problem 1: Nginx konnte Host "backend" nicht auflösen}
\begin{itemize}
\item Ursache: In \texttt{nginx.conf} stand \texttt{proxy\_pass http://backend:5000}, aber der Backend-Container heißt \texttt{fitness-api}.
\item Lösung: \texttt{backend} $\rightarrow$ \texttt{fitness-api} in \texttt{nginx.conf} ändern, Image neu bauen.
\end{itemize}
\textbf{Problem 2: Frontend lud Assets mit Pfad \texttt{/app/...}}
\begin{itemize}
\item Ursache: In \texttt{vite.config.ts} war \texttt{base: "/app/"} für PWA-Zwecke gesetzt.
\item Lösung: Geändert auf \texttt{base: "/"}.
\end{itemize}
\textbf{Problem 3: API-Anfragen gingen an lokale IP \texttt{192.168.178.189}}
\begin{itemize}
\item Ursache: Im Client stand \texttt{const API\_BASE = "http://192.168.178.189:5107"}.
\item Lösung: Geändert auf relative Pfade (\texttt{/api/workouts}), sodass Nginx die Anfragen per Reverse Proxy ans Backend weiterleitet.
\end{itemize}
\textbf{Problem 4: Doppeltes \texttt{/api} im Pfad}
\begin{itemize}
\item Ursache: Client hatte \texttt{API\_BASE = "/api"} und Endpunkte begannen ebenfalls mit \texttt{/api}.
\item Ergebnis: Anfragen gingen an \texttt{/api/api/workouts}.
\item Lösung: API-Base auf leeren String gesetzt und Endpunkte mit \texttt{/api/} beginnen lassen.
\end{itemize}
\subsection{Zusammenfassung}
Nach diesem Schritt ist die Fitness-App produktiv auf dem VPS im Einsatz:
\begin{itemize}
\item Die App ist unter \texttt{http://185.209.229.167} weltweit erreichbar
\item Workouts werden persistent in einer SQLite-Datenbank gespeichert
\item Docker-Volumes stellen sicher, dass Daten Container-Neustarts überleben
\item Das Backend läuft auf .NET 8, das Frontend auf Nginx
\item Ein Reverse Proxy (Nginx) leitet API-Anfragen intern an das Backend weiter
\end{itemize}