Visual Studio 2015 Preview contains many new and exciting features to support cross-platform mobile development, web & cloud development, IDE productivity enhancements, and more.
برای به درست آوردن این اعتماد، یکی از بالاترین الویت ها، آینده نگری معماری نرم افزار و ساخت یک معماری داده است که باید هر دو قوی و به اندازه کافی ایمن باشد، این دو برای راضی کردن مستاجران و کلاینت هایی که علاقمند هستند کنترل دادههای حیاتی تجارت خود را به شخص سومی واگذار نمایند، موثر است در حالی که برای اداره کردن و نگهداری مقرون به صرفه است.
سه روش مدیریت چند مستاجری داده
- دیتابیسهای جداگانه برای هر مستاجر
- دیتابیس مشترک و schema جداگانه برای هر مستاجر
- دیتابیس مشترک و schema مشترک
انتخاب روش مناسب برای برنامه شما به عوامل زیر بستگی دارد :
- سایز دیتابیس هر مستاجر
- تعداد مستاجران
- تعداد کاربران هر مستاجر
- نرخ رشد مستاجر
- نرخ رشد دیتابیس مستاجر
- امنیت
- هزینه
1) دیتابیسهای جداگانه برای هر مستاجر :
ذخیره سازی دادههای مستاجران در دیتابیسهای جداگانه سادهترین روش است. در این روش هر مستاجر یک دیتابیس دارد. منابع و کدهای برنامه معمولا در سرور بین همه مستاجران مشترک است اما هر مستاجر مجموعه ای از داده دارد که بطور منطقی از سایر مستاجران جدا شده است.
مزایا :
- امنیت بیشتر
- سهولت سفارشی سازی برای هر مستاجر
- سهولت نگهداری ( Backup و Restore ) برای هر مستاجر
معایب:
- برای نگهداری سخت افزار قوی مورد نیاز است
- این روش هزینه بیشتری برای تجهیزات ( Backup و Restore ) برای هر مستاجر دارد
2) دیتابیس مشترک و schema جداگانه برای هر مستاجر :
خدمات دهی به چندین مستاجر در یک دیتابیس مشترک اما هر مستاجر یک مجموعه از جداول گروهبندی شده دارد که با Schema جدا شده است که برای هر مستاجر الزامی است.
مزایا :
- برای دیتابیس برنامههای کوچک مناسب است. وقتی تعداد جداول برای هر مشتری کم است
- هزینه کمتری نسبت به روش اول دارد
- برای مشتریانی که نگران امنیت هستند، سطح منطقی مناسبی برای جداسازی داده ه وجود دارد
معایب:
- اطلاعات مستاجران در صورت بروز خطا به سختی restore میشود
- مدیریت آن برای دیتابیسهای بزرگ مشکل است
3) دیتابیس مشترک و schema مشترک :
این روش شمامل یک دیتابیس و یک مجموعه از جداول برای چندین مستاجر است. دادههای جدول میتواند شامل رکوردهای هر مستاجر باشد
مزایا :
- در مقایسه با روش قبلی، کمترین هزینه سخت افزاری را دارد
- میتواند مستاجران بیشتری رادر هر سرور پشتیبانی کند
- قابلیت بروز رسانی آسان در یک جا برای همه مستاجران
- مدیریت آسان دیتابیس و خطا و Backup و Restore
- امنیت بیشتری مورد نیاز است تا مطمئن شوید هیچکس به اطلاعات سایر مستاجران دسترسی ندارد.
- میتواند روی کارایی کوئریها تاثیر بگذارد چون تعداد رکوردها زیاد است.
- بروزرسانی و سفارشی کردن فقط برای یک مستاجر سخت است
منابع :
http://msdn.microsoft.com/en-us/library/aa479086.aspx
http://www.codeproject.com/Articles/51334/Multi-Tenants-Database-Architecture
نشانه های طراحی ضعیف
برای آنکه طراحی قوی و درست را یاد بگیریم، لازم است که نشانههای طراحی ضعیف را بدانیم. این نشانهها عبارتند از:
۱- Rigidity (انعطاف ناپذیری): یک ماژول انعطاف ناپذیر است، اگر یک تغییر در آن، منجر به تغییرات در سایر ماژولها گردد. هر چه میزان تغییرات آبشاری بیشتر باشد، نرم افزار خشکتر و غیر منعطفتر است.
۲- Fragility (شکنندگی): وقتی که تغییر در قسمتی از نرم افزار باعث به بروز اشکال در بخشهای دیگر شود.
۳- Immobility (تحرک ناپذیری): وقتی نتوان قسمت هایی از نرم افزار را در جاهای دیگر استفاده نمود و یا به کار گیری آن هزینه و ریسک بالایی داشته باشد.
۴- Viscosity (لزجی): وقتی حفظ طراحی اصولی پروژه مشکل باشد، میگوییم پروژه لزج شده است. به عنوان مثال وقتی تغییری در پروژه به دو صورت اصولی و غیر اصولی قابل انجام باشد و روش غیر اصولی راحتتر باشد، میگوییم لزج شده است. البته لزجی محیط هم وجود دارد مثلا انجام کار به صورت اصولی نیاز به Build کل پروژه دارد که زیاد طول میکشد.
۵- Needless Complexity (پیچیدگی اضافی): زمانی که امکانات بدون استفاده در نرم افزار قرار گیرند.
۶- Needless Repetition (تکرارهای اضافی): وقتی که کدهایی با منطق یکسان در جاهای مختلف برنامه کپی میشوند، این مشکلات رخ میدهند.
۷- Opacity (ابهام): وقتی که فهمیدن یک ماژول سخت شود، رخ میدهد و کد برنامه مبهم بوده و قابل فهم نباشد.
چرا نرم افزار تمایل به پوسیدگی دارد؟
در روشهای غیر چابک یکی از دلایل اصلی پوسیدگی، عدم تطابق نرم افزار با تغییرات درخواستی است. لازم است که این تغییرات به سرعت انجام شوند و ممکن است که توسعه دهندگان از طراحی ابتدایی اطلاعی نداشته باشند. با این حال ممکن است تغییرایی قابل انجام باشد ولی برخی از آنها طراحی اصلی را نقض میکنند. ما نباید تغییرات نیازمندیها را مقصر بدانیم. باید طراحی ما قابلیت تطبیق با تغییرات را داشته باشد.
یک تیم چابک از تغییرات استقبال میکند. وقت بسیار کمی را روی طراحی اولیه کل کار میگذارد و سعی میکند که طراحی سیستم را تا جایی که ممکن است ساده و تمیز نگه دارد با استفاده از تستهای واحد و یکپارچه از آن محافظت کند. این طراحی را انعطاف پذیر میکند. تیم از قابلیت انعطاف پذیری برای بهبود همیشگی طراحی استفاده میکند. بنابراین در هر تکرار نرم افزاری خواهیم داشت که نیازمندیهای آن تکرار را برآورده میکند.
سلام. یک نرم افزار رو در نظر بگیرید که هر روز، به صورت خودکار، قیمت 1000 کالا را از 100 وبسایت مختلف دریافت و در دیتابیس ذخیره می کند. پرسش من اینه که کدوم روش زیر برای طراحی دیتابیس این نرم افزار و همچنین منطق کاری اون اصولی تر و بهینه تره:
روش اول: در این روش، دیتابیس نرم افزار، شامل چهار جدول (جدول سایت ها – جدول کالاها – جدول تاریخ ها و جدول ثبت اطلاعات روزانه قیمت کالا) است که جدول ثبت تاریخ و جدول اطلاعات روزانه، ارتباط یک به چند دارند (جدول اطلاعات روزانه، شامل یک کلید خارجی از جدول کالاها، یک کلید خارجی از جدول سایت ها و یک کلید خارجی از جدول تاریخ ها است) و نرمالسازی جداول هم انجام شده.
در این روش، هر روز، به صورت خودکار، نرم افزار، ابتدا تاریخ جاری را در جدول تاریخ ها ثبت می کند و سپس، به ازای هر سایت و به ازای هر کالا، یک رکورد (شامل شناسه تاریخ جاری، شناسه سایت و شناسه کالا) را به همراه قیمت روز آن کالا که از وبسایت مربوطه دریافت کرده است، در جدول اطلاعات روزانه ثبت می کند.
بنابراین، حدود 100 هزار رکورد در هر روز باید در این جدول ثبت گردد که هم زمانبر است و هم ممکن است هنگام ثبت این داده ها در دیتابیس، خطایی رخ دهد (فرض کنیم که صحت داده ها اهمیت زیادی داشته باشد). پس لازم است پس از ثبت این رکوردها، همه آن ها مجددا چک شود که عملیات ثبت ناقص انجام نشده باشد و این هم مدتی زمان لازم دارد. ضمن آنکه حجم دیتابیس نیز روز به روز افزایش خواهد یافت.
مشکل دیگر این روش آن است که اگر در آینده، تعداد سایت ها یا تعداد کالاها افزایش یابد، اجرای این روش دشوارتر و احتمال خطای آن بیشتر می شود. مثلا اگر تعداد سایت ها 1000 و تعداد کالاها 1000 شود، هر روز باید یک میلیون رکورد در جدول اطلاعات روزانه ثبت و کنترل گردد.
روش دوم: در این روش، دیتابیس شامل همان چهار جدول قبلی است. با این تفاوت که در جدول اطلاعات روزانه، یک فیلد از نوع XML و یا یک فیلد از نوع رشته ای برای ثبت تمامی اطلاعات لازم کالا در یک روز (شامل شناسه تاریخ، شناسه کالا، شناسه سایتی که اطلاعات از آن دریافت می شود و سایر مشخصات لازم) در نظر گرفته شده است. بنابراین، در این روش، در ابتدای شروع کار نرم افزار، ابتدا به ازای هر کالا، یک رکورد در جدول اطلاعات روزانه ثبت می شود. بنابراین، کلا 1000 رکورد در این جدول وجود خواهد داشت. سپس، هر روز، عملیات دریافت اطلاعات هر کالا از سایت های مربوطه انجام شده و فایل XML یا رشته مربوط به رکورد متناظر با آن کالا ویرایش می شود. بنابراین، در این روش، همیشه کلا 1000 رکورد وجود دارد که باید به صورت روزانه، بروزرسانی شوند و این موضوع، سرعت کار را افزایش و میزان خطا و همچنین حجم دیتابیس را نیز کاهش می دهد. البته به نظرم این روش غیر اصولی است و اصول نرمال سازی و ... در مورد اون رعایت نشده و خیلی ایراد داره.
حالا پرسش من اینه که اگر محدودیت زمان برای دریافت و ثبت داده های روزانه وجود داشته باشد (مثلا کلیه اطلاعات باید در 5 دقیقه دریافت و ثبت شود) و در آینده نیز تعداد رکوردهایی که باید به صورت روزانه ثبت شود افزایش پیدا کند، کدامیک از این دو روش، از نظر طراحی و هم از نظر کاربری نرم افزار، بهینه تر و اصولی تر است؟ آیا برای رسیدن به سرعت و کارایی بالاتر، مجاز هستیم از روش دوم که غیر اصولی به نظر میرسه استفاده کنیم؟ آیا ساخت یک دیتابیس ترکیبی از SQL و فایل های XML به این شکل کار درستی است؟ در غیر اینصورت، روش اصولی برای نرم افزارهایی که باید در هر عملیات، تعداد زیادی داده را ثبت و مدیریت کنند چیست؟ با تشکر.
با استفاده از YAML، دستورالعملهای سرویسهای مختلف را نوشته و با یک دستور همهی آنها را با هم اجرا مینماییم. از compose در تمامی مراحل production, staging, development, testing و همچنین CI workflow استفاده میشود.
برای استفاده از compose سه عمل زیر باید انجام شود:
1- ساخت و تعریف dockerfile برای هر سرویس.
2- ساخت و تعریف docker-compose.yml. بنابراین هر سرویس میتواند در محیط ایزولهی خود اجرا شود.
3- اجرای دستور docker-compose up.
در قسمتهای قبلی مراحل ساخت و اجرای imageها درون کانتینر و همچنین متصل کردن آنها را به شبکه، بررسی کرده ایم؛ اما در این قسمت میخواهیم با استفاده از docker-compose مدیریت build و اجرای همهی imageها را بر عهده بگیریم.
عملا با این ساختار، قابلیت ایجاد شبکه، volume و غیره را خواهیم داشت. بنابراین با استفاده از این config توانایی توزیع برنامه را فقط با یک فایل YAML، خواهیم داشت.
ایجاد پروژه:
فرض کنید نرم افزار ما از دو سرویس Nodejsی همچنین یک دیتابیس Mongo تشکیل شده است. در نهایت باید به چیزی شبیه به تصویر زیر برسیم:
دایرکتوری root این پروژه از دو پوشه به نامهای nodeapp1 و nodeapp2 تشکیل شده است که داخل هر کدام یک فایل index.js و همچنین package.json و dockerfile وجود دارد؛ همانند مطالب پیشین.
اما چیزی که اینجا اضافه شده است، فایل docker-compose.yml جهت مدیریت و اجرای این برنامه میباشد که حاوی ساختار زیر است:
version: '3' networks: shared-network: services: nodeapp1: image: nodeapp1 build: context: nodeapp1 dockerfile: dockerfile ports: - "8181:80" networks: - shared-network nodeapp2: image: nodeapp2 build: context: nodeapp2 dockerfile: dockerfile ports: - "8182:80" networks: - shared-network mongo: image: mongo ports: - "27017:27017" networks: - shared-network
2) برای مشخص کردن سرویسهای این برنامه از services استفاده کرده و آنها را تعریف مینماییم.
3) سرویس nodeapp1 که قرار است تصویری به نام nodeapp1 را ایجاد کند (هدف آن build کردن اولین سرویس میباشد. همانطور که مشخص است context برنامه، اسم پوشهی nodeapp1 درون ریشهی پروژه است. ضمن اینکه نام dockerfile را هم درون آن پوشه بدان اضافه کردهایم).
4) پورت 8181 را بر روی پورت 80 درون این کانتینر هدایت میکنیم.
5) این سرویس، درون شبکهی ایجاد شدهی shared-network قرار میگیرد.
5) سرویس nodeapp2 را هم به همین شکل اضافه میکنیم.
6) سرویس mongo قرار نیست هیچ کدی را build کند و هدف، فقط اجرای mongo درون شبکهی shared-network است که بقیه سرویسها بتوانند بدان وصل شوند.
برای ساخت و اجرا نیز در ریشهی این پروژه، ترمینال خود را باز کرده و دستورات زیر را وارد مینماییم:
برای build کردن:
docker-compose build
docker-compose up
docker-compose down
docker-compose stop
docker-compose start
و اگر بخواهیم بعد از build کردن، بصورت خودکار نیز اجرا شود، از دستور زیر استفاده میکنیم:
docker-compose run --build
dockerfile هر دو سرویس نیز بصورت ساده همانند مطالب پیشین در نظر گرفته شدهاست:
FROM node COPY . /var/www WORKDIR /var/www RUN npm i EXPOSE 80 ENTRYPOINT node index
در صورتیکه بخواهیم نگاهی هم به کدهای نوشته شده بیندازیم، نکتهی جالبی مد نظر قرار میگیرد؛ بطور مثال از آنجائیکه همهی کانتینرهای اجرا شده، درون یک شبکه هستند، برای فراخوانی سرویسهای دیگر کافیست با نامشان صدا زده شوند. بطور مثال در nodeapp1 برای فراخوانی nodeapp2 به راحتی با نام صدا زده شده است و احتیاجی به فراخوانی با ip نیست. کدهای زیر مربوط به فایل index.js در سرویس nodeapp1 میباشند (بدلیل اینکه روی پورت 80 درون کانتینر قرار گرفتهاست، دیگر لازم به وارد نمودن پورت نبودیم؛ در غیر اینصورت بطور مثال باید درخواستی بصورت http://nodeapp2:5000 را ارسال مینمودیم):
const express = require('express'); const fetch = require('node-fetch'); const app = express(); app.get('/', async (req, res) => { let response = await fetch("http://nodeapp2/"); text = await response.text(); res.send(text); }) app.listen(80, () => console.log(`listening on port 80!`))
نکته: override کردن composeها نیز قابل انجام است. بدین معنا که شما یک نسخه برای build و استفاده در محیط development و نسخههای دیگری بطور مثال برای production خود تعریف مینمایید؛ مثلا روی پروداکشن، environment variablesهای متفاوتی را در نظر میگیرید. YAML زیر را مشاهده کنید:
version: '3' services: nodeapp1: environment: - PRODUCTION: 'true' nodeapp2: environment: - PRODUCTION: 'true'
فرض کنید که قرار است YAML فوق بر روی فایل قبلی، بازنویسی شود؛ با استفاده از دستور زیر:
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up
تمام کدهای فوق از اینجا «node.rar» قابل دریافت میباشد.
چرا SQL در حال شکست NoSQL است؟
SQL is back. Not just because writing glue code to kludge together NoSQL tools is annoying. Not just because retraining workforces to learn a myriad of new languages is hard. Not just because standards can be a good thing.
But also because the world is filled with data. It surrounds us, binds us. At first, we relied on our human senses and sensory nervous systems to process it. Now our software and hardware systems are also getting smart enough to help us. And as we collect more and more data to make better sense of our world, the complexity of our systems to store, process, analyze, and visualize that data will only continue to grow as well.
Domain Driven Design: The Good Parts
The greenfield project started out so promising. Instead of devolving into big ball of mud, the team decided to apply domain-driven design principles. Ubiquitous language, proper boundaries, encapsulation, it all made sense.
But along the way, something went completely and utterly wrong. It started with arguments on the proper way of implementing aggregates and entities. Arguments began over project and folder structure. Someone read a blog post that repositories are evil, and ORMs the devil incarnate. Another read that relational databases are last century, we need to store everything as a stream of events. Then came the actor model and frameworks that sounded like someone clearing their throat. Instead of a nice, clean architecture, the team chased the next new approach without ever actually shipping anything.
Beyond the endless technical arguments it causes, domain-driven design can actually produce great software. We have to look past the hype into the true value of DDD, what it can bring to our organizations and how it can enable us to build quality systems. With the advent of microservices, DDD is more important than ever - but only if we can get to the good parts.
مشاهدهی جزئیات اطلاعات سرور و بستههای نصب شدهی بر روی آن
در نگارشهای قبل از RTM، با فراخوانی app.UseRuntimeInfoPage در متد Configure کلاس Startup، ریز اطلاعاتی از وضعیت سرور و بستههای موجود در آن با مراجعهی به آدرس http://site/runtimeinfo نمایش داده میشدند. این مورد خاص از نگارش RTM حذف شدهاست (احتمالا به دلایل امنیتی). البته اگر علاقمند به بررسی کدهای آن باشید، هنوز تاریخچهی آن در GitHub موجود است .
مدیریت خطاها در برنامههای ASP.NET Core 1.0
به متد Configure کلاس Startup مراجعه کرد و یک سطر استثناء را به ابتدای کدهای Middleware انتهایی آن اضافه کنید:
public void Configure(IApplicationBuilder app) { app.Run(async context => { throw new Exception("Generic Error"); await context.Response.WriteAsync("Hello DNT!"); }); }
در این حالت اگر برنامه را اجرا کنیم، این خروجی را دریافت خواهیم کرد:
و اگر به وضعیت بازگشت داده شدهی از طرف سرور دقت کنیم، فقط internal server error است:
در اینجا برخلاف نگارشهای قبلی ASP.NET، دیگر حتی صفحهی زرد رنگ معروف نمایش خطاها (yellow screen of death) نیز فعال نیستند. برای فعال سازی آن نیاز است Middleware مرتبط با آنرا به نحو ذیل به برنامه معرفی کنیم:
public void Configure(IApplicationBuilder app) { app.UseDeveloperExceptionPage();
به دلایل امنیتی و عدم نشت اطلاعات سمت سرور و خصوصا عدم امکان دیباگ از راه دور برنامه توسط مهاجمین، این Middleware به صورت پیش فرض فعال نیست.
بنابراین این سؤال مطرح میشود که چگونه میتوان این صفحه را تنها در حین توسعهی برنامه نمایش داد؟
پاسخ آن به نحوهی طراحی متد Configure در کلاس Startup بر میگردد. این متد امضای ثابتی را ندارد. هر تعداد سرویسی را که نیاز داشتید، میتوانید به عنوان پارامتر این متد معرفی کنید و کار تزریق وابستگیها و نمونه سازی آنها، توسط امکانات توکار ASP.NET Core به صورت خودکار انجام میشود. برای مثال سرویس IApplicationBuilder، یکی از سرویسهای توکار ASP.NET Core است و برای تنظیم آن نیازی نیست تا کار خاصی را انجام دهیم. به همین جهت است که صرفا معرفی اینترفیس آن در این متد، وهلهای را از سازندهی برنامه در اختیار ما قرار میدهد. سرویسها را در مطلبی جداگانه مورد بررسی قرار خواهیم داد، اما فعلا جهت تکمیل بحث باید درنظر داشت که یکی دیگر از سرویسهای توکار ASP.NET Core، به نام IHostingEnvironment، اطلاعاتی را در مورد محیطی که برنامه را در آن اجرا میکنیم در اختیار ما قرار میدهد:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); }
این متغیر محیطی میتواند سه مقدار Development, Staging و Production را داشته باشد و بر اساس این متغیر و مقدار آن است که یکی از سه متد ذیل مفهوم پیدا میکنند و true یا false را باز میگردانند:
if(env.IsDevelopment()){ } if(env.IsProduction()){ } if(env.IsStaging()){ }
نمایش و مدیریت خطاها در حالت Production
از app.UseDeveloperExceptionPage صرفا در حالت توسعه استفاده کنید؛ چون اطلاعات نمایش داده شدهی توسط آن، بیش از اندازه برای مهاجمین مفید است. اما در حالت توزیع نهایی بر روی سرور چه باید کرد؟
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler(errorHandlingPath: "/MyControllerName/SomeActionMethodName"); }
به علاوه در اینجا (در این قسمت خاص برنامه که توسط پارامتر errorHandlingPath مشخص شدهاست) با استفاده از قطعه کد ذیل، دسترسی کاملی را به اطلاعات خطای رخ داده، جهت ثبت و لاگ آن دارید:
var feature = HttpContext.Features.Get<IExceptionHandlerFeature>(); var error = feature?.Error;
بررسی میانافزار StatusCode
این میان افزار برای مدیریت responseهایی که status code آنها بین 400 تا 600 هستند، طراحی شدهاست. بر اساس این شمارهها، میتوان خطای خاصی را بازگشت داده و یا کاربر را به یک صفحه یا کنترلر خاصی در برنامه، هدایت کرد.
در حالت عادی ثبت آن
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseStatusCodePages(); app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler(errorHandlingPath: "/MyControllerName/SomeActionMethodName"); } }
برای نمونه در اینجا مسیری درخواست داده شدهاست که توسط برنامه پردازش نمیشود و وجود ندارد.
اگر خواستید تا status code واقعی، به کاربر بازگشت داده شود، اما خروجی نمایش داده شده را سفارشی سازی کنید، میتوانید از متد UseStatusCodePagesWithReExecute استفاده نمائید:
app.UseStatusCodePagesWithReExecute("/MyControllerName/SomeActionMethodName/{0}");
WebAssembly, or Wasm, is on its way to becoming the next big thing in software development, allowing us to develop more reusable code across programming stacks. It will also enable the deployment of smaller packages more securely. This talk will focus on the view of a .NET developer using WebAssembly in their projects, whether client-side, server-side, or plugins.
Agenda
0:00:00 – Introduction
0:01:44 – Presentation Start
0:04:36 – Wasm on the client
0:08:27 – Wasm on the server
0:11:51 – ASP.NET Core in Wasi
0:21:41 – Wasm in the cloud
0:32:38 – Wasm for plugins
0:36:07 – Wasm plugins samples
0:49:05 – .NET 8 and the future
0:55:08 – Who’s working on this?
1:03:13 – Outro
دوره کامل Docker
Complete Docker Course - From BEGINNER to PRO! (Learn Containers)
Learn Docker and containers to improve your software systems! 🐳 📦
This course covers everything from getting started all the way through building a containerized web application and deploying it to the cloud!
Timestamps:
00:00 - Introduction
04:40 - History and motivation
30:27 - Technology overview
40:30 - Installation and set up
47:15 - Using 3rd party container images
48:06 - Understanding container data and docker volumes
1:13:00 - Demo application
1:28:37 - Building container images
2:23:46 - Container registries
2:33:45 - Running containers
3:02:36 - Container security
3:06:58 - Interacting with Docker objects
3:18:36 - Development workflow
3:52:05 - Ephemeral environments with Shipyard
4:07:17 - Deploying containers
4:42:59 - Final wrap up