├── .gitignore ├── CHANGES.txt ├── MANIFEST.in ├── README.md ├── docker_tutorial ├── __init__.py ├── admin.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto__add_subscriber__add_unique_subscriber_email_from_level__add_dock.py │ ├── 0003_auto__add_field_tutorialuser_http_user_agent__add_field_tutorialuser_h.py │ ├── 0004_auto__del_field_tutorialuser_http_accept_encoding__add_field_tutorialu.py │ ├── 0005_auto__chg_field_tutorialuser_http_user_agent__chg_field_tutorialuser_h.py │ └── __init__.py ├── models.py ├── static │ ├── css │ │ ├── tutorial-style.css │ │ └── tutorial-style.less │ ├── img │ │ ├── circle-green.svg │ │ ├── docker-letters.png │ │ ├── docker-topicon.png │ │ ├── email.png │ │ ├── facebook.png │ │ ├── favicon.png │ │ ├── fullscreen_exit_32x32.png │ │ └── twitter.png │ ├── js │ │ ├── steps.coffee │ │ ├── steps.js │ │ ├── terminal.coffee │ │ └── terminal.js │ └── lib │ │ ├── css │ │ └── jquery.terminal.css │ │ └── js │ │ ├── jquery-1.7.1.min.js │ │ ├── jquery.mousewheel-min.js │ │ ├── jquery.terminal-0.7.4.js │ │ ├── jquery.terminal-0.7.4.min.js │ │ └── sugar-1.3.9.min.js ├── templates │ └── tutorial │ │ ├── snippet.html │ │ ├── stats.html │ │ └── testpage.html ├── tests │ ├── __init__.py │ └── test_views.py ├── urls.py ├── utils.py └── views.py ├── dockerfile_tutorial ├── __init__.py ├── static │ ├── css │ │ └── dockerfile_tutorial.css │ └── js │ │ ├── dockerfile_tutorial.js │ │ ├── dockerfile_tutorial_level1.js │ │ ├── dockerfile_tutorial_level2.js │ │ └── jquery.cookie.js ├── templates │ └── dockerfile │ │ ├── _dockerfile.html │ │ ├── introduction.html │ │ ├── level1.html │ │ └── level2.html └── urls.py ├── dotcloud.yml ├── requirements.txt ├── runtests.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .coverage 3 | *.pyc 4 | DockerTutorial.egg-info 5 | MANIFEST 6 | dist/ 7 | htmlcov 8 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 0.1, Aug 13 2013 -- Initial release 2 | The initial release of the Docker tutorial 3 | 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | include README.md 3 | recursive-include docker_tutorial/static * 4 | recursive-include docker_tutorial/templates * 5 | 6 | recursive-include dockerfile_tutorial/static * 7 | recursive-include dockerfile_tutorial/templates * 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Docker Tutorial 2 | =============== 3 | 4 | The Docker tutorial is an interactive learning environment to get familiar with the Docker commandline. 5 | 6 | 7 | Simple Usage 8 | ------------ 9 | 10 | Generally this application is used in the www.docker.io website. It's source can be found on 11 | https://github.com/dotcloud/www.docker.io/. By installing that you will get this app 'for free' as a 12 | dependency. 13 | 14 | However, this app is made to also be usable inside other Django applications. All that is required is to 15 | add git+https://github.com/dhrp/docker-tutorial.git#egg=DockerTutorial-dev to your requirements.txt 16 | and it will be installed by pip upon pip install -r requirements.txt 17 | 18 | To include it 19 | * Make sure your "host" app uses the same python environment 20 | * In your "host" app settings include "docker_tutorial" 21 | * in a template {% include 'tutorial/snippet.html' %} 22 | * in your urls.py add url(r'^tutorial/', include('docker_tutorial.urls')), 23 | * in your settings make sure you include the session middleware: 24 | 25 | 26 | When you want to make changes 27 | ----------------------------- 28 | 29 | * First create or switch to a virtual environment in which you have the "host" app into which you would 30 | like to embed the tutorial. e.g. a clone of the the Docker website (before you ran install) 31 | * Clone this repository: 32 | git clone https://github.com/dhrp/docker-tutorial.git 33 | * Switch to the dir: 34 | cd docker-tutorial 35 | * Install the application with the -e (editable) flag. 36 | pip install -e . 37 | 38 | This will setup the symlinks such that you don't need to run setup.py every time you want to see a 39 | change. i.e. your local repository is now linked into the environment. 40 | 41 | Running the unit tests 42 | ---------------------- 43 | 44 | * ./runtests.py 45 | 46 | Running code coverage 47 | --------------------- 48 | 49 | * coverage run ./runtests.py && coverage html --include="./docker_tutorial/*" 50 | 51 | Happy coding! -------------------------------------------------------------------------------- /docker_tutorial/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'thatcher' 2 | -------------------------------------------------------------------------------- /docker_tutorial/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.sessions.models import Session 3 | from .models import * 4 | 5 | 6 | class SessionAdmin(admin.ModelAdmin): 7 | def _session_data(self, obj): 8 | return obj.get_decoded() 9 | list_display = ['session_key', '_session_data', 'expire_date'] 10 | admin.site.register(Session, SessionAdmin) 11 | 12 | 13 | class TutorialEventInline(admin.TabularInline): 14 | model = TutorialEvent 15 | readonly_fields = ('timestamp', 'time_elapsed', 'question', 'type', 'command',) 16 | fields = ['question', 'type', 'command', 'timestamp', 'time_elapsed'] 17 | extra = 0 18 | 19 | 20 | class DockerfileEvenInline(admin.TabularInline): 21 | model = DockerfileEvent 22 | readonly_fields = ('timestamp', 'level', 'item', 'errors', ) 23 | fields = ['level', 'item', 'errors', 'timestamp'] 24 | extra = 0 25 | 26 | 27 | class TutorialUserAdmin(admin.ModelAdmin): 28 | model = TutorialUser 29 | list_display = ['id', 'session_key', 'label', 'timestamp'] 30 | inlines = [TutorialEventInline, DockerfileEvenInline] 31 | admin.site.register(TutorialUser, TutorialUserAdmin) 32 | 33 | 34 | class TutorialEventAdmin(admin.ModelAdmin): 35 | model = TutorialEvent 36 | readonly_fields = ('id', 'timestamp', 'time_elapsed') 37 | fields = ['id', 'user', 'type', 'question', 'command', 'timestamp', 'time_elapsed'] 38 | list_display = ['id', 'user','type', 'question', 'timestamp'] 39 | admin.site.register(TutorialEvent, TutorialEventAdmin) 40 | 41 | 42 | class TutorialEventFeedback(TutorialEvent): 43 | class Meta: 44 | proxy = True 45 | 46 | 47 | class TutorialEventFeedbackAdmin(admin.ModelAdmin): 48 | model = TutorialEvent 49 | readonly_fields = ('id', 'timestamp', 'time_elapsed') 50 | list_display = ['id', 'user', 'question', 'feedback', 'timestamp'] 51 | list_filter = ('type',) 52 | admin.site.register(TutorialEventFeedback, TutorialEventFeedbackAdmin) 53 | 54 | 55 | class SubscriberAdmin(admin.ModelAdmin): 56 | model = Subscriber 57 | list_display = ['email', 'from_level'] 58 | admin.site.register(Subscriber, SubscriberAdmin) 59 | 60 | 61 | class DockerfileEventAdmin(admin.ModelAdmin): 62 | model = DockerfileEvent 63 | list_display = ['id', 'timestamp', 'item', 'level', 'errors'] 64 | admin.site.register(DockerfileEvent, DockerfileEventAdmin) -------------------------------------------------------------------------------- /docker_tutorial/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'TutorialUser' 12 | db.create_table(u'docker_tutorial_tutorialuser', ( 13 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('session_key', self.gf('django.db.models.fields.CharField')(max_length=80)), 15 | ('timestamp', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, auto_now=True, blank=True)), 16 | ('label', self.gf('django.db.models.fields.CharField')(default='', max_length=80, null=True, blank=True)), 17 | )) 18 | db.send_create_signal(u'docker_tutorial', ['TutorialUser']) 19 | 20 | # Adding model 'TutorialEvent' 21 | db.create_table(u'docker_tutorial_tutorialevent', ( 22 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 23 | ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['docker_tutorial.TutorialUser'])), 24 | ('type', self.gf('django.db.models.fields.CharField')(max_length=15)), 25 | ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), 26 | ('question', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), 27 | ('command', self.gf('django.db.models.fields.CharField')(default='', max_length=80, blank=True)), 28 | ('feedback', self.gf('django.db.models.fields.CharField')(default='', max_length=2000, blank=True)), 29 | )) 30 | db.send_create_signal(u'docker_tutorial', ['TutorialEvent']) 31 | 32 | 33 | def backwards(self, orm): 34 | # Deleting model 'TutorialUser' 35 | db.delete_table(u'docker_tutorial_tutorialuser') 36 | 37 | # Deleting model 'TutorialEvent' 38 | db.delete_table(u'docker_tutorial_tutorialevent') 39 | 40 | 41 | models = { 42 | u'docker_tutorial.tutorialevent': { 43 | 'Meta': {'object_name': 'TutorialEvent'}, 44 | 'command': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'blank': 'True'}), 45 | 'feedback': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '2000', 'blank': 'True'}), 46 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 47 | 'question': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 48 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), 49 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '15'}), 50 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"}) 51 | }, 52 | u'docker_tutorial.tutorialuser': { 53 | 'Meta': {'object_name': 'TutorialUser'}, 54 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 55 | 'label': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'null': 'True', 'blank': 'True'}), 56 | 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '80'}), 57 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}) 58 | } 59 | } 60 | 61 | complete_apps = ['docker_tutorial'] -------------------------------------------------------------------------------- /docker_tutorial/migrations/0002_auto__add_subscriber__add_unique_subscriber_email_from_level__add_dock.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'Subscriber' 12 | db.create_table(u'docker_tutorial_subscriber', ( 13 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('email', self.gf('django.db.models.fields.EmailField')(max_length=80)), 15 | ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['docker_tutorial.TutorialUser'])), 16 | ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), 17 | ('from_level', self.gf('django.db.models.fields.IntegerField')()), 18 | )) 19 | db.send_create_signal(u'docker_tutorial', ['Subscriber']) 20 | 21 | # Adding unique constraint on 'Subscriber', fields ['email', 'from_level'] 22 | db.create_unique(u'docker_tutorial_subscriber', ['email', 'from_level']) 23 | 24 | # Adding model 'DockerfileEvent' 25 | db.create_table(u'docker_tutorial_dockerfileevent', ( 26 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 27 | ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['docker_tutorial.TutorialUser'])), 28 | ('item', self.gf('django.db.models.fields.CharField')(max_length=15)), 29 | ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), 30 | ('level', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), 31 | ('errors', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), 32 | )) 33 | db.send_create_signal(u'docker_tutorial', ['DockerfileEvent']) 34 | 35 | 36 | def backwards(self, orm): 37 | # Removing unique constraint on 'Subscriber', fields ['email', 'from_level'] 38 | db.delete_unique(u'docker_tutorial_subscriber', ['email', 'from_level']) 39 | 40 | # Deleting model 'Subscriber' 41 | db.delete_table(u'docker_tutorial_subscriber') 42 | 43 | # Deleting model 'DockerfileEvent' 44 | db.delete_table(u'docker_tutorial_dockerfileevent') 45 | 46 | 47 | models = { 48 | u'docker_tutorial.dockerfileevent': { 49 | 'Meta': {'object_name': 'DockerfileEvent'}, 50 | 'errors': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 51 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 52 | 'item': ('django.db.models.fields.CharField', [], {'max_length': '15'}), 53 | 'level': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 54 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), 55 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"}) 56 | }, 57 | u'docker_tutorial.subscriber': { 58 | 'Meta': {'unique_together': "(('email', 'from_level'),)", 'object_name': 'Subscriber'}, 59 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '80'}), 60 | 'from_level': ('django.db.models.fields.IntegerField', [], {}), 61 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 62 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), 63 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"}) 64 | }, 65 | u'docker_tutorial.tutorialevent': { 66 | 'Meta': {'object_name': 'TutorialEvent'}, 67 | 'command': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'blank': 'True'}), 68 | 'feedback': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '2000', 'blank': 'True'}), 69 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 70 | 'question': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 71 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), 72 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '15'}), 73 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"}) 74 | }, 75 | u'docker_tutorial.tutorialuser': { 76 | 'Meta': {'object_name': 'TutorialUser'}, 77 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 78 | 'label': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'null': 'True', 'blank': 'True'}), 79 | 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '80'}), 80 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}) 81 | } 82 | } 83 | 84 | complete_apps = ['docker_tutorial'] -------------------------------------------------------------------------------- /docker_tutorial/migrations/0003_auto__add_field_tutorialuser_http_user_agent__add_field_tutorialuser_h.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'TutorialUser.http_user_agent' 12 | db.add_column(u'docker_tutorial_tutorialuser', 'http_user_agent', 13 | self.gf('django.db.models.fields.CharField')(default='', max_length=256, blank=True), 14 | keep_default=False) 15 | 16 | # Adding field 'TutorialUser.http_remote_address' 17 | db.add_column(u'docker_tutorial_tutorialuser', 'http_remote_address', 18 | self.gf('django.db.models.fields.CharField')(default='', max_length=32, blank=True), 19 | keep_default=False) 20 | 21 | # Adding field 'TutorialUser.http_accept_language' 22 | db.add_column(u'docker_tutorial_tutorialuser', 'http_accept_language', 23 | self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True), 24 | keep_default=False) 25 | 26 | # Adding field 'TutorialUser.http_accept_encoding' 27 | db.add_column(u'docker_tutorial_tutorialuser', 'http_accept_encoding', 28 | self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True), 29 | keep_default=False) 30 | 31 | # Adding field 'TutorialUser.http_referrer' 32 | db.add_column(u'docker_tutorial_tutorialuser', 'http_referrer', 33 | self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True), 34 | keep_default=False) 35 | 36 | 37 | # Changing field 'TutorialUser.label' 38 | db.alter_column(u'docker_tutorial_tutorialuser', 'label', self.gf('django.db.models.fields.CharField')(max_length=80)) 39 | 40 | def backwards(self, orm): 41 | # Deleting field 'TutorialUser.http_user_agent' 42 | db.delete_column(u'docker_tutorial_tutorialuser', 'http_user_agent') 43 | 44 | # Deleting field 'TutorialUser.http_remote_address' 45 | db.delete_column(u'docker_tutorial_tutorialuser', 'http_remote_address') 46 | 47 | # Deleting field 'TutorialUser.http_accept_language' 48 | db.delete_column(u'docker_tutorial_tutorialuser', 'http_accept_language') 49 | 50 | # Deleting field 'TutorialUser.http_accept_encoding' 51 | db.delete_column(u'docker_tutorial_tutorialuser', 'http_accept_encoding') 52 | 53 | # Deleting field 'TutorialUser.http_referrer' 54 | db.delete_column(u'docker_tutorial_tutorialuser', 'http_referrer') 55 | 56 | 57 | # Changing field 'TutorialUser.label' 58 | db.alter_column(u'docker_tutorial_tutorialuser', 'label', self.gf('django.db.models.fields.CharField')(max_length=80, null=True)) 59 | 60 | models = { 61 | u'docker_tutorial.dockerfileevent': { 62 | 'Meta': {'object_name': 'DockerfileEvent'}, 63 | 'errors': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 64 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 65 | 'item': ('django.db.models.fields.CharField', [], {'max_length': '15'}), 66 | 'level': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 67 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), 68 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"}) 69 | }, 70 | u'docker_tutorial.subscriber': { 71 | 'Meta': {'unique_together': "(('email', 'from_level'),)", 'object_name': 'Subscriber'}, 72 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '80'}), 73 | 'from_level': ('django.db.models.fields.IntegerField', [], {}), 74 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 75 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), 76 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"}) 77 | }, 78 | u'docker_tutorial.tutorialevent': { 79 | 'Meta': {'object_name': 'TutorialEvent'}, 80 | 'command': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'blank': 'True'}), 81 | 'feedback': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '2000', 'blank': 'True'}), 82 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 83 | 'question': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 84 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), 85 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '15'}), 86 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"}) 87 | }, 88 | u'docker_tutorial.tutorialuser': { 89 | 'Meta': {'object_name': 'TutorialUser'}, 90 | 'http_accept_encoding': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), 91 | 'http_accept_language': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), 92 | 'http_referrer': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), 93 | 'http_remote_address': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 94 | 'http_user_agent': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}), 95 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 96 | 'label': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'blank': 'True'}), 97 | 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '80'}), 98 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}) 99 | } 100 | } 101 | 102 | complete_apps = ['docker_tutorial'] -------------------------------------------------------------------------------- /docker_tutorial/migrations/0004_auto__del_field_tutorialuser_http_accept_encoding__add_field_tutorialu.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Deleting field 'TutorialUser.http_accept_encoding' 12 | db.delete_column(u'docker_tutorial_tutorialuser', 'http_accept_encoding') 13 | 14 | # Adding field 'TutorialUser.http_real_remote_address' 15 | db.add_column(u'docker_tutorial_tutorialuser', 'http_real_remote_address', 16 | self.gf('django.db.models.fields.CharField')(default='', max_length=32, blank=True), 17 | keep_default=False) 18 | 19 | 20 | def backwards(self, orm): 21 | # Adding field 'TutorialUser.http_accept_encoding' 22 | db.add_column(u'docker_tutorial_tutorialuser', 'http_accept_encoding', 23 | self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True), 24 | keep_default=False) 25 | 26 | # Deleting field 'TutorialUser.http_real_remote_address' 27 | db.delete_column(u'docker_tutorial_tutorialuser', 'http_real_remote_address') 28 | 29 | 30 | models = { 31 | u'docker_tutorial.dockerfileevent': { 32 | 'Meta': {'object_name': 'DockerfileEvent'}, 33 | 'errors': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 34 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 35 | 'item': ('django.db.models.fields.CharField', [], {'max_length': '15'}), 36 | 'level': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 37 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), 38 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"}) 39 | }, 40 | u'docker_tutorial.subscriber': { 41 | 'Meta': {'unique_together': "(('email', 'from_level'),)", 'object_name': 'Subscriber'}, 42 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '80'}), 43 | 'from_level': ('django.db.models.fields.IntegerField', [], {}), 44 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 45 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), 46 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"}) 47 | }, 48 | u'docker_tutorial.tutorialevent': { 49 | 'Meta': {'object_name': 'TutorialEvent'}, 50 | 'command': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'blank': 'True'}), 51 | 'feedback': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '2000', 'blank': 'True'}), 52 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 53 | 'question': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 54 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), 55 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '15'}), 56 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"}) 57 | }, 58 | u'docker_tutorial.tutorialuser': { 59 | 'Meta': {'object_name': 'TutorialUser'}, 60 | 'http_accept_language': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), 61 | 'http_real_remote_address': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 62 | 'http_referrer': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), 63 | 'http_remote_address': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 64 | 'http_user_agent': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}), 65 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 66 | 'label': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'blank': 'True'}), 67 | 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '80'}), 68 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}) 69 | } 70 | } 71 | 72 | complete_apps = ['docker_tutorial'] -------------------------------------------------------------------------------- /docker_tutorial/migrations/0005_auto__chg_field_tutorialuser_http_user_agent__chg_field_tutorialuser_h.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | 12 | # Changing field 'TutorialUser.http_user_agent' 13 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_user_agent', self.gf('django.db.models.fields.TextField')()) 14 | 15 | # Changing field 'TutorialUser.http_real_remote_address' 16 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_real_remote_address', self.gf('django.db.models.fields.TextField')()) 17 | 18 | # Changing field 'TutorialUser.http_remote_address' 19 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_remote_address', self.gf('django.db.models.fields.TextField')()) 20 | 21 | # Changing field 'TutorialUser.http_accept_language' 22 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_accept_language', self.gf('django.db.models.fields.TextField')()) 23 | 24 | # Changing field 'TutorialUser.http_referrer' 25 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_referrer', self.gf('django.db.models.fields.TextField')()) 26 | 27 | # Changing field 'TutorialUser.session_key' 28 | db.alter_column(u'docker_tutorial_tutorialuser', 'session_key', self.gf('django.db.models.fields.CharField')(unique=True, max_length=40)) 29 | # Adding unique constraint on 'TutorialUser', fields ['session_key'] 30 | db.create_unique(u'docker_tutorial_tutorialuser', ['session_key']) 31 | 32 | 33 | def backwards(self, orm): 34 | # Removing unique constraint on 'TutorialUser', fields ['session_key'] 35 | db.delete_unique(u'docker_tutorial_tutorialuser', ['session_key']) 36 | 37 | 38 | # Changing field 'TutorialUser.http_user_agent' 39 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_user_agent', self.gf('django.db.models.fields.CharField')(max_length=256)) 40 | 41 | # Changing field 'TutorialUser.http_real_remote_address' 42 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_real_remote_address', self.gf('django.db.models.fields.CharField')(max_length=32)) 43 | 44 | # Changing field 'TutorialUser.http_remote_address' 45 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_remote_address', self.gf('django.db.models.fields.CharField')(max_length=32)) 46 | 47 | # Changing field 'TutorialUser.http_accept_language' 48 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_accept_language', self.gf('django.db.models.fields.CharField')(max_length=128)) 49 | 50 | # Changing field 'TutorialUser.http_referrer' 51 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_referrer', self.gf('django.db.models.fields.CharField')(max_length=128)) 52 | 53 | # Changing field 'TutorialUser.session_key' 54 | db.alter_column(u'docker_tutorial_tutorialuser', 'session_key', self.gf('django.db.models.fields.CharField')(max_length=80)) 55 | 56 | models = { 57 | u'docker_tutorial.dockerfileevent': { 58 | 'Meta': {'object_name': 'DockerfileEvent'}, 59 | 'errors': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 60 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 61 | 'item': ('django.db.models.fields.CharField', [], {'max_length': '15'}), 62 | 'level': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 63 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), 64 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"}) 65 | }, 66 | u'docker_tutorial.subscriber': { 67 | 'Meta': {'unique_together': "(('email', 'from_level'),)", 'object_name': 'Subscriber'}, 68 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '80'}), 69 | 'from_level': ('django.db.models.fields.IntegerField', [], {}), 70 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 71 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), 72 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"}) 73 | }, 74 | u'docker_tutorial.tutorialevent': { 75 | 'Meta': {'object_name': 'TutorialEvent'}, 76 | 'command': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'blank': 'True'}), 77 | 'feedback': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '2000', 'blank': 'True'}), 78 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 79 | 'question': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 80 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), 81 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '15'}), 82 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"}) 83 | }, 84 | u'docker_tutorial.tutorialuser': { 85 | 'Meta': {'object_name': 'TutorialUser'}, 86 | 'http_accept_language': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), 87 | 'http_real_remote_address': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), 88 | 'http_referrer': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), 89 | 'http_remote_address': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), 90 | 'http_user_agent': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), 91 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 92 | 'label': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'blank': 'True'}), 93 | 'session_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}), 94 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}) 95 | } 96 | } 97 | 98 | complete_apps = ['docker_tutorial'] -------------------------------------------------------------------------------- /docker_tutorial/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhrp/docker-tutorial/6edf3d18e6a71a22fbd7bd1a0f371fee924d615a/docker_tutorial/migrations/__init__.py -------------------------------------------------------------------------------- /docker_tutorial/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from datetime import datetime 3 | 4 | __author__ = 'thatcher' 5 | 6 | 7 | class TutorialUser(models.Model): 8 | session_key = models.CharField(max_length=40, unique=True) # natural key 9 | timestamp = models.DateTimeField(auto_now=True, default=datetime.now) 10 | label = models.CharField(max_length=80, default='', blank=True) 11 | http_user_agent = models.TextField(default='', blank=True) 12 | http_remote_address = models.TextField(default='', blank=True) 13 | http_real_remote_address = models.TextField(default='', blank=True) 14 | http_accept_language = models.TextField(default='', blank=True) 15 | http_referrer = models.TextField(default='', blank=True) 16 | 17 | def __unicode__(self): 18 | return u"%s" % (self.id) 19 | 20 | 21 | class TutorialEvent(models.Model): 22 | 23 | NONE = 'none' 24 | START = 'start' 25 | COMMAND = 'command' 26 | NEXT = 'next' 27 | FEEDBACK = 'feedback' 28 | PEEK = 'peek' 29 | COMPLETE = 'complete' 30 | 31 | 32 | EVENT_TYPES = ( 33 | (NONE, u'No type set (debug)'), 34 | (START, u'Start tutorial'), 35 | (COMMAND, u'Command given'), 36 | (NEXT, u'Next Question'), 37 | (FEEDBACK, u'Feedback'), 38 | (PEEK, u'Peek'), 39 | (COMPLETE, u'Tutorial Complete') 40 | ) 41 | 42 | user = models.ForeignKey(TutorialUser) 43 | type = models.CharField(max_length=15, choices=EVENT_TYPES, blank=False) 44 | timestamp = models.DateTimeField(auto_now=True) 45 | question = models.IntegerField(null=True, blank=True) 46 | command = models.CharField(max_length=80, blank=True, default="") 47 | feedback = models.CharField(max_length=2000, blank=True, default="") 48 | 49 | @property 50 | def time_elapsed(self): 51 | time_elapsed = self.timestamp - TutorialEvent.objects.filter(user=self.user).order_by('pk')[0].timestamp 52 | return time_elapsed.seconds 53 | 54 | def __unicode__(self): 55 | return str(self.id) 56 | 57 | 58 | class DockerfileEvent(models.Model): 59 | 60 | user = models.ForeignKey(TutorialUser) 61 | item = models.CharField(max_length=15, blank=False) 62 | timestamp = models.DateTimeField(auto_now=True) 63 | level = models.IntegerField(null=True, blank=True) 64 | errors = models.IntegerField(null=True, blank=True) 65 | 66 | @property 67 | def time_elapsed(self): 68 | time_elapsed = self.timestamp - TutorialEvent.objects.filter(user=self.user).order_by('pk')[0].timestamp 69 | return time_elapsed.seconds 70 | 71 | def __unicode__(self): 72 | return str(self.id) 73 | 74 | 75 | class Subscriber(models.Model): 76 | 77 | email = models.EmailField(max_length=80) 78 | user = models.ForeignKey(TutorialUser) 79 | timestamp = models.DateTimeField(auto_now=True) 80 | from_level = models.IntegerField() 81 | 82 | class Meta: 83 | unique_together = ("email", "from_level") 84 | 85 | def __unicode__(self): 86 | return u"{}".format(self.email) 87 | -------------------------------------------------------------------------------- /docker_tutorial/static/css/tutorial-style.css: -------------------------------------------------------------------------------- 1 | .debug { 2 | border: 2px red dotted; 3 | background: #361919; 4 | /* Fall-back for browsers that don't support rgba */ 5 | 6 | background: rgba(54, 25, 25, 0.5); 7 | box-sizing: border-box; 8 | } 9 | /* 10 | Styles largely duplicates from the docker website 11 | */ 12 | .tutorial { 13 | font-family: "Cabin", "Helvetica Neue", Helvetica, Arial, sans-serif; 14 | font-size: 14px; 15 | line-height: 20px; 16 | color: #333333; 17 | } 18 | .tutorial h1, 19 | .tutorial h2, 20 | .tutorial h3, 21 | .tutorial h4, 22 | .tutorial h5, 23 | .tutorial h6 { 24 | margin: 10px 0; 25 | font-family: inherit; 26 | font-weight: normal; 27 | line-height: 20px; 28 | color: inherit; 29 | text-rendering: optimizelegibility; 30 | } 31 | .tutorial h1, 32 | .tutorial h2, 33 | .tutorial h3 { 34 | line-height: 40px; 35 | } 36 | .tutorial img { 37 | max-width: 100%; 38 | width: auto\9; 39 | height: auto; 40 | vertical-align: middle; 41 | border: 0; 42 | -ms-interpolation-mode: bicubic; 43 | } 44 | .tutorial select, 45 | .tutorial textarea, 46 | .tutorial input[type="text"], 47 | .tutorial input[type="password"], 48 | .tutorial input[type="datetime"], 49 | .tutorial input[type="datetime-local"], 50 | .tutorial input[type="date"], 51 | .tutorial input[type="month"], 52 | .tutorial input[type="time"], 53 | .tutorial input[type="week"], 54 | .tutorial input[type="number"], 55 | .tutorial input[type="email"], 56 | .tutorial input[type="url"], 57 | .tutorial input[type="search"], 58 | .tutorial input[type="tel"], 59 | .tutorial input[type="color"], 60 | .tutorial .uneditable-input { 61 | display: inline-block; 62 | height: 20px; 63 | padding: 4px 6px; 64 | margin-bottom: 10px; 65 | font-size: 14px; 66 | line-height: 20px; 67 | color: #555555; 68 | -webkit-border-radius: 4px; 69 | -moz-border-radius: 4px; 70 | border-radius: 4px; 71 | vertical-align: middle; 72 | } 73 | .tutorial button, 74 | .tutorial input, 75 | .tutorial select, 76 | .tutorial textarea { 77 | margin: 0; 78 | font-size: 100%; 79 | vertical-align: middle; 80 | } 81 | .tutorial label, 82 | .tutorial input, 83 | .tutorial button, 84 | .tutorial select, 85 | .tutorial textarea { 86 | font-size: 14px; 87 | font-weight: normal; 88 | line-height: 20px; 89 | } 90 | .tutorial code, 91 | .tutorial pre { 92 | padding: 0 3px 2px; 93 | font-family: Monaco, Menlo, Consolas, "Courier New", monospace; 94 | font-size: 12px; 95 | color: #333333; 96 | -webkit-border-radius: 3px; 97 | -moz-border-radius: 3px; 98 | border-radius: 3px; 99 | } 100 | .tutorial code { 101 | padding: 2px 4px; 102 | color: #d14; 103 | background-color: #f7f7f9; 104 | border: 1px solid #e1e1e8; 105 | white-space: nowrap; 106 | } 107 | /* 108 | New (non dupe) styles 109 | */ 110 | .tutorial p { 111 | margin-top: 0px; 112 | margin-bottom: 5px; 113 | line-height: 1.3em; 114 | } 115 | .tutorial h1, 116 | .tutorial h2, 117 | .tutorial h3, 118 | .tutorial h4 { 119 | margin-top: 5px; 120 | margin-bottom: 7px; 121 | color: #394D54; 122 | } 123 | .tutorial h3 { 124 | font-size: 24px; 125 | } 126 | .tutorial h4 { 127 | color: #008bb8; 128 | } 129 | #command .box { 130 | border: 1px grey solid; 131 | border-radius: 5px; 132 | padding: 15px; 133 | box-sizing: border-box; 134 | color: #a9a9a9; 135 | font-style: italic; 136 | } 137 | #commandShownText, 138 | #tipShownText { 139 | color: black !important; 140 | font-style: normal !important; 141 | } 142 | #command .hidden, 143 | #tips .hidden { 144 | display: none; 145 | visibility: hidden; 146 | height: 1px; 147 | width: 1px; 148 | } 149 | #ajax { 150 | position: absolute; 151 | bottom: 0; 152 | margin: 20px; 153 | } 154 | #resulttext { 155 | border-radius: 5px; 156 | box-sizing: border-box; 157 | color: #106e34; 158 | font-weight: bold; 159 | } 160 | #feedback { 161 | font-size: 0.4em; 162 | border: 1px #444444 solid; 163 | background-color: black; 164 | position: absolute; 165 | right: 0; 166 | bottom: 0px; 167 | margin-bottom: 5px; 168 | } 169 | #feedback input { 170 | margin: 5px; 171 | border-radius: 0px; 172 | position: relative; 173 | width: 400px; 174 | background-color: #dcdcdc; 175 | } 176 | #feedback button { 177 | margin: 5px 5px 5px 0; 178 | height: 30px; 179 | } 180 | #actions button { 181 | width: 45%; 182 | } 183 | .hidden { 184 | display: none; 185 | visibility: hidden; 186 | height: 1px; 187 | width: 1px; 188 | } 189 | /* styles for when it is not fullsize */ 190 | #overlay { 191 | position: relative; 192 | width: 100%; 193 | } 194 | #overlay.startsize { 195 | border: 3px solid #7fafc1; 196 | height: 300px; 197 | padding: 15px; 198 | } 199 | #overlay.fullsize { 200 | position: absolute; 201 | top: 0; 202 | right: 0; 203 | bottom: 0; 204 | left: 0; 205 | width: auto; 206 | height: auto; 207 | background-color: white; 208 | z-index: 1982; 209 | } 210 | #tutorialTop { 211 | display: none; 212 | position: absolute; 213 | height: 70px; 214 | left: 0; 215 | right: 0; 216 | width: 100%; 217 | background-color: #394D54; 218 | } 219 | #tutorialTop img { 220 | margin-bottom: 20px; 221 | display: inline-block; 222 | } 223 | #tutorialTop h1, 224 | #tutorialTop h2 { 225 | display: inline-block; 226 | color: white; 227 | margin-top: 0px; 228 | margin-bottom: 0px; 229 | } 230 | #tutorialTop h1 { 231 | font-size: 22px; 232 | color: white; 233 | font-weight: normal; 234 | margin-top: 20px; 235 | } 236 | #main { 237 | position: absolute; 238 | top: 0; 239 | right: 0; 240 | bottom: 0; 241 | left: 0; 242 | } 243 | #leftside.fullsize { 244 | box-sizing: border-box; 245 | width: 35%; 246 | position: absolute; 247 | display: block; 248 | top: 0; 249 | right: 0; 250 | bottom: 0; 251 | left: 0; 252 | margin-top: 70px; 253 | overflow-y: scroll; 254 | } 255 | #rightside { 256 | margin-top: 70px; 257 | } 258 | #instructions, 259 | #results, 260 | #actions, 261 | #tips, 262 | #command { 263 | padding: 5px 10px 0 10px; 264 | } 265 | #instructions .text, 266 | #results .text, 267 | #actions .text, 268 | #tips .text, 269 | #command .text, 270 | #instructions .assignment, 271 | #results .assignment, 272 | #actions .assignment, 273 | #tips .assignment, 274 | #command .assignment, 275 | #instructions .tiptext, 276 | #results .tiptext, 277 | #actions .tiptext, 278 | #tips .tiptext, 279 | #command .tiptext { 280 | margin-left: 60px; 281 | } 282 | #instructions .text, 283 | #results .text, 284 | #actions .text, 285 | #tips .text, 286 | #command .text { 287 | font-size: 16px; 288 | } 289 | #instructions circle { 290 | fill: #FF8100; 291 | } 292 | #results { 293 | z-index: 20; 294 | background-color: #e7e7e7; 295 | width: 400px; 296 | padding: 10px; 297 | position: absolute; 298 | box-sizing: border-box; 299 | right: 0; 300 | top: 0; 301 | margin-top: 100px; 302 | } 303 | #results circle { 304 | fill: green; 305 | } 306 | #results.intermediate circle { 307 | fill: #008bb8; 308 | } 309 | #results button { 310 | position: relative; 311 | float: right; 312 | margin-top: 10px; 313 | } 314 | #instructions .assignment { 315 | margin-top: 20px; 316 | border: 2px grey solid; 317 | padding: 15px; 318 | padding-top: 5px; 319 | } 320 | .circle { 321 | position: relative; 322 | float: left; 323 | font-size: 18px; 324 | margin-top: 15px; 325 | } 326 | #terminal { 327 | box-sizing: border-box; 328 | position: absolute; 329 | display: block; 330 | top: 0; 331 | right: 0; 332 | bottom: 0; 333 | left: 35%; 334 | } 335 | #terminal.smallsize { 336 | margin: 10px; 337 | margin-bottom: 65px; 338 | } 339 | #terminal.fullsize { 340 | margin: 0; 341 | margin-right: 15px; 342 | margin-top: 70px; 343 | padding-bottom: 0px; 344 | margin-bottom: 65px; 345 | } 346 | #terminal-extenstion { 347 | box-sizing: border-box; 348 | position: absolute; 349 | display: block; 350 | top: 0; 351 | right: 0; 352 | bottom: 0; 353 | left: 35%; 354 | background-color: black; 355 | margin-bottom: 0px; 356 | } 357 | #terminal-extenstion.smallsize { 358 | margin: 10px; 359 | } 360 | #terminal-extenstion.fullsize { 361 | margin: 0; 362 | margin-top: 70px; 363 | } 364 | #terminal a { 365 | color: #AAA; 366 | text-decoration: none; 367 | font-weight: normal; 368 | } 369 | #starttext { 370 | width: 200px; 371 | margin: 10px; 372 | } 373 | .hide-when-small { 374 | display: none; 375 | } 376 | .docker-btn { 377 | border: none; 378 | color: white; 379 | } 380 | .docker-btn-large { 381 | font-size: 22px; 382 | padding: 10px 15px 10px 15px; 383 | } 384 | .docker-btn-blue { 385 | border: none; 386 | color: white; 387 | background: #008bb8; 388 | /* Old browsers */ 389 | 390 | background: -moz-linear-gradient(top, #008bb8 0%, #00617f 100%); 391 | /* FF3.6+ */ 392 | 393 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #008bb8), color-stop(100%, #00617f)); 394 | /* Chrome,Safari4+ */ 395 | 396 | background: -webkit-linear-gradient(top, #008bb8 0%, #00617f 100%); 397 | /* Chrome10+,Safari5.1+ */ 398 | 399 | background: -o-linear-gradient(top, #008bb8 0%, #00617f 100%); 400 | /* Opera 11.10+ */ 401 | 402 | background: -ms-linear-gradient(top, #008bb8 0%, #00617f 100%); 403 | /* IE10+ */ 404 | 405 | background: linear-gradient(to bottom, #008bb8 0%, #00617f 100%); 406 | /* W3C */ 407 | 408 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#008bb8', endColorstr='#00617f', GradientType=0); 409 | /* IE6-9 */ 410 | 411 | } 412 | .docker-btn-blue:enabled:active { 413 | border: none; 414 | color: white; 415 | background: #00617f; 416 | /* Old browsers */ 417 | 418 | background: -moz-linear-gradient(top, #00617f 0%, #008bb8 100%); 419 | /* FF3.6+ */ 420 | 421 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #00617f), color-stop(100%, #008bb8)); 422 | /* Chrome,Safari4+ */ 423 | 424 | background: -webkit-linear-gradient(top, #00617f 0%, #008bb8 100%); 425 | /* Chrome10+,Safari5.1+ */ 426 | 427 | background: -o-linear-gradient(top, #00617f 0%, #008bb8 100%); 428 | /* Opera 11.10+ */ 429 | 430 | background: -ms-linear-gradient(top, #00617f 0%, #008bb8 100%); 431 | /* IE10+ */ 432 | 433 | background: linear-gradient(to bottom, #00617f 0%, #008bb8 100%); 434 | /* W3C */ 435 | 436 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00617f', endColorstr='#008bb8', GradientType=0); 437 | /* IE6-9 */ 438 | 439 | } 440 | .docker-btn-blue:disabled { 441 | color: white; 442 | background: #4f727c; 443 | /* Old browsers */ 444 | 445 | background: -moz-linear-gradient(top, #4f727c 0%, #89acb7 100%); 446 | /* FF3.6+ */ 447 | 448 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #4f727c), color-stop(100%, #89acb7)); 449 | /* Chrome,Safari4+ */ 450 | 451 | background: -webkit-linear-gradient(top, #4f727c 0%, #89acb7 100%); 452 | /* Chrome10+,Safari5.1+ */ 453 | 454 | background: -o-linear-gradient(top, #4f727c 0%, #89acb7 100%); 455 | /* Opera 11.10+ */ 456 | 457 | background: -ms-linear-gradient(top, #4f727c 0%, #89acb7 100%); 458 | /* IE10+ */ 459 | 460 | background: linear-gradient(to bottom, #4f727c 0%, #89acb7 100%); 461 | /* W3C */ 462 | 463 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#4f727c', endColorstr='#89acb7', GradientType=0); 464 | /* IE6-9 */ 465 | 466 | } 467 | #tutorialTop .closeButton { 468 | position: relative; 469 | float: right; 470 | margin: 6px; 471 | width: 18px; 472 | height: 18px; 473 | } 474 | #progress-indicator { 475 | margin-top: 20px; 476 | margin-right: 20px; 477 | float: right; 478 | cursor: hand; 479 | } 480 | .progress-marker { 481 | position: relative; 482 | float: left; 483 | margin-right: 5px; 484 | cursor: hand; 485 | } 486 | .progress-marker svg { 487 | width: 30px; 488 | height: 30px; 489 | cursor: pointer; 490 | } 491 | .progress-marker circle { 492 | fill: #eaeaea; 493 | } 494 | .progress-marker text { 495 | fill: black; 496 | } 497 | .progress-marker.complete circle { 498 | fill: green; 499 | } 500 | .progress-marker:hover circle { 501 | fill: #01b0ee; 502 | } 503 | .progress-marker:active circle { 504 | fill: #ff810a; 505 | } 506 | .progress-marker.active circle { 507 | fill: #ff810a; 508 | } 509 | .complete img { 510 | height: 45px; 511 | width: 45px; 512 | margin-top: 10px; 513 | margin-right: 10px; 514 | } 515 | .complete ol li { 516 | margin-bottom: 10px; 517 | font-weight: bold; 518 | } 519 | -------------------------------------------------------------------------------- /docker_tutorial/static/css/tutorial-style.less: -------------------------------------------------------------------------------- 1 | @informationblue: #008BB8; 2 | @tutorialTopHeight: 60px; 3 | @greySuperLight: #E7E7E7; 4 | @topBarHeight: 70px; 5 | 6 | 7 | .debug { 8 | border: 2px red dotted; 9 | background: rgb(54, 25, 25); /* Fall-back for browsers that don't support rgba */ 10 | background: rgba(54, 25, 25, .5); 11 | box-sizing: border-box; 12 | } 13 | 14 | /* 15 | Styles largely duplicates from the docker website 16 | */ 17 | 18 | .tutorial { 19 | font-family: "Cabin", "Helvetica Neue", Helvetica, Arial, sans-serif; 20 | font-size: 14px; 21 | line-height: 20px; 22 | color: #333333; 23 | 24 | h1, h2, h3, h4, h5, h6 { 25 | margin: 10px 0; 26 | font-family: inherit; 27 | font-weight: normal; 28 | line-height: 20px; 29 | color: inherit; 30 | text-rendering: optimizelegibility; 31 | } 32 | 33 | h1, h2, h3 { 34 | line-height: 40px; 35 | } 36 | 37 | img { 38 | max-width: 100%; 39 | width: auto\9; 40 | height: auto; 41 | vertical-align: middle; 42 | border: 0; 43 | -ms-interpolation-mode: bicubic; 44 | } 45 | 46 | select, textarea, input[type="text"], input[type="password"], input[type="datetime"], input[type="datetime-local"], input[type="date"], input[type="month"], input[type="time"], input[type="week"], input[type="number"], input[type="email"], input[type="url"], input[type="search"], input[type="tel"], input[type="color"], .uneditable-input { 47 | display: inline-block; 48 | height: 20px; 49 | padding: 4px 6px; 50 | margin-bottom: 10px; 51 | font-size: 14px; 52 | line-height: 20px; 53 | color: #555555; 54 | -webkit-border-radius: 4px; 55 | -moz-border-radius: 4px; 56 | border-radius: 4px; 57 | vertical-align: middle; 58 | } 59 | 60 | button, input, select, textarea { 61 | margin: 0; 62 | font-size: 100%; 63 | vertical-align: middle; 64 | } 65 | 66 | label, input, button, select, textarea { 67 | font-size: 14px; 68 | font-weight: normal; 69 | line-height: 20px; 70 | } 71 | 72 | code, pre { 73 | padding: 0 3px 2px; 74 | font-family: Monaco, Menlo, Consolas, "Courier New", monospace; 75 | font-size: 12px; 76 | color: #333333; 77 | -webkit-border-radius: 3px; 78 | -moz-border-radius: 3px; 79 | border-radius: 3px; 80 | } 81 | 82 | code { 83 | padding: 2px 4px; 84 | color: #d14; 85 | background-color: #f7f7f9; 86 | border: 1px solid #e1e1e8; 87 | white-space: nowrap; 88 | } 89 | 90 | } 91 | 92 | /* 93 | New (non dupe) styles 94 | */ 95 | 96 | 97 | .tutorial { 98 | p { 99 | margin-top: 0px; 100 | margin-bottom: 5px; 101 | line-height: 1.3em; 102 | } 103 | 104 | h1, h2, h3, h4{ 105 | margin-top: 5px; 106 | margin-bottom: 7px; 107 | color: #394D54; 108 | } 109 | 110 | h3 { 111 | font-size: 24px; 112 | } 113 | 114 | h4 { 115 | color: @informationblue; 116 | // margin-bottom: 5px; 117 | } 118 | } 119 | 120 | 121 | #command .box{ 122 | // .debug; 123 | border: 1px grey solid; 124 | border-radius: 5px; 125 | padding: 15px; 126 | box-sizing: border-box; 127 | color: #a9a9a9; 128 | font-style: italic; 129 | // style="width: 100%; height: 100%" 130 | } 131 | 132 | #commandShownText, #tipShownText { 133 | color: black !important; 134 | font-style: normal !important; 135 | } 136 | 137 | #command .hidden, #tips .hidden { 138 | display: none; 139 | visibility: hidden; 140 | height: 1px; width: 1px; 141 | } 142 | 143 | #ajax { 144 | position: absolute; 145 | bottom: 0; 146 | margin: 20px; 147 | // border: 1px red dotted; 148 | } 149 | 150 | #resulttext { 151 | border-radius: 5px; 152 | box-sizing: border-box; 153 | color: #106e34; 154 | font-weight: bold; 155 | } 156 | 157 | 158 | 159 | #feedback { 160 | font-size: 0.4em; 161 | border: 1px #444444 solid; 162 | background-color: black; 163 | position: absolute; 164 | right: 0; 165 | bottom: 0px; 166 | margin-bottom: 5px; 167 | 168 | input { 169 | margin: 5px; 170 | border-radius: 0px; 171 | position: relative; 172 | width: 400px; 173 | background-color: #dcdcdc; 174 | } 175 | button { 176 | margin: 5px 5px 5px 0; 177 | height: 30px; 178 | } 179 | } 180 | 181 | 182 | #actions button { 183 | width: 45%; 184 | } 185 | 186 | .hidden { 187 | display: none; 188 | visibility: hidden; 189 | height: 1px; 190 | width: 1px; 191 | } 192 | 193 | 194 | /* styles for when it is not fullsize */ 195 | 196 | 197 | //#overlay .hide-when-full { 198 | // display: none; 199 | //} 200 | 201 | 202 | #overlay { 203 | position: relative; 204 | width: 100%; 205 | 206 | &.startsize { 207 | border: 3px solid #7fafc1; 208 | height: 300px; 209 | padding: 15px; 210 | } 211 | 212 | &.fullsize { 213 | position: absolute; 214 | top: 0; 215 | right: 0; 216 | bottom: 0; 217 | left: 0; 218 | width: auto; 219 | height: auto; 220 | 221 | // here we set a background color for the overlay, so the elements below don't shine through 222 | background-color: white; 223 | z-index: 1982; 224 | } 225 | } 226 | 227 | 228 | #tutorialTop { 229 | 230 | display: none; 231 | 232 | position: absolute; 233 | height: @topBarHeight; 234 | left: 0; 235 | right: 0; 236 | width: 100%; 237 | background-color: #394D54; 238 | 239 | img { 240 | margin-bottom: 20px; 241 | display: inline-block; 242 | } 243 | h1, h2 { 244 | display: inline-block; 245 | color: white; 246 | margin-top: 0px; 247 | margin-bottom: 0px; 248 | } 249 | 250 | h1 { 251 | font-size: 22px; 252 | // font-size: 2.2rem; 253 | color: white; 254 | font-weight: normal; 255 | margin-top: 20px; 256 | } 257 | } 258 | 259 | 260 | 261 | #main { 262 | position: absolute; 263 | top: 0; 264 | right: 0; 265 | bottom: 0; 266 | left: 0; 267 | 268 | &.fullsize { 269 | // margin-top: 0px; 270 | // top: @topBarHeight; 271 | } 272 | 273 | } 274 | 275 | #leftside { 276 | &.fullsize { 277 | box-sizing: border-box; 278 | width: 35%; 279 | position: absolute; 280 | display: block; 281 | top: 0; 282 | right: 0; 283 | bottom: 0; 284 | left: 0; 285 | 286 | margin-top: @topBarHeight; 287 | overflow-y: scroll; 288 | // .debug; 289 | } 290 | 291 | } 292 | 293 | #rightside { 294 | margin-top: @topBarHeight; 295 | } 296 | 297 | 298 | #instructions, #results, #actions, #tips, #command { 299 | padding: 5px 10px 0 10px; 300 | .text, .assignment, .tiptext { 301 | margin-left: 60px; 302 | } 303 | .text { 304 | font-size: 16px; 305 | } 306 | } 307 | 308 | #instructions { 309 | circle { fill: #FF8100; } 310 | } 311 | 312 | #results { 313 | position: absolute; 314 | z-index: 20; 315 | background-color: @greySuperLight; 316 | width: 400px; 317 | // min-height: 130px; 318 | padding: 10px; 319 | position:absolute; 320 | box-sizing: border-box; 321 | right:0; 322 | top: 0; 323 | margin-top: @topBarHeight + 30px; 324 | 325 | circle { fill: green; } 326 | 327 | &.intermediate { 328 | circle { fill: @informationblue } 329 | } 330 | 331 | button { 332 | position: relative; 333 | float: right; 334 | margin-top: 10px; 335 | // margin-bottom: 15px; 336 | } 337 | } 338 | 339 | #instructions { 340 | // .debug; 341 | .assignment { 342 | margin-top: 20px; 343 | border: 2px grey solid; 344 | padding: 15px; 345 | padding-top: 5px; 346 | } 347 | } 348 | 349 | .circle { 350 | position: relative; 351 | float: left; 352 | font-size: 18px; 353 | margin-top: 15px; 354 | } 355 | 356 | #terminal { 357 | box-sizing: border-box; 358 | position: absolute; 359 | display: block; 360 | top: 0; 361 | right: 0; 362 | bottom: 0; 363 | left: 35%; 364 | 365 | &.smallsize { 366 | margin: 10px; 367 | // padding-bottom: 10px; 368 | margin-bottom: 65px; 369 | } 370 | 371 | &.fullsize { 372 | margin: 0; 373 | margin-right: 15px; 374 | margin-top: @topBarHeight; 375 | padding-bottom: 0px; 376 | margin-bottom: 65px; 377 | } 378 | } 379 | 380 | #terminal-extenstion { 381 | box-sizing: border-box; 382 | position: absolute; 383 | display: block; 384 | top: 0; 385 | right: 0; 386 | bottom: 0; 387 | left: 35%; 388 | 389 | background-color: black; 390 | margin-bottom: 0px; 391 | 392 | &.smallsize { 393 | margin: 10px; 394 | } 395 | 396 | &.fullsize { 397 | margin: 0; 398 | margin-top: @topBarHeight; 399 | } 400 | 401 | } 402 | 403 | #terminal a { 404 | color: #AAA; 405 | text-decoration: none; 406 | font-weight: normal; 407 | } 408 | 409 | 410 | #starttext { 411 | width: 200px; 412 | margin: 10px; 413 | } 414 | 415 | .hide-when-small { 416 | display: none; 417 | } 418 | 419 | .hide-when-full { 420 | // display: none; 421 | } 422 | 423 | .docker-btn { 424 | border: none; 425 | color: white; 426 | 427 | } 428 | 429 | .docker-btn-large { 430 | font-size: 22px; 431 | padding: 10px 15px 10px 15px; 432 | } 433 | 434 | .docker-btn-blue { 435 | 436 | .docker-btn; 437 | 438 | background: #008bb8; /* Old browsers */ 439 | background: -moz-linear-gradient(top, #008bb8 0%, #00617f 100%); /* FF3.6+ */ 440 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#008bb8), color-stop(100%,#00617f)); /* Chrome,Safari4+ */ 441 | background: -webkit-linear-gradient(top, #008bb8 0%,#00617f 100%); /* Chrome10+,Safari5.1+ */ 442 | background: -o-linear-gradient(top, #008bb8 0%,#00617f 100%); /* Opera 11.10+ */ 443 | background: -ms-linear-gradient(top, #008bb8 0%,#00617f 100%); /* IE10+ */ 444 | background: linear-gradient(to bottom, #008bb8 0%,#00617f 100%); /* W3C */ 445 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#008bb8', endColorstr='#00617f',GradientType=0 ); /* IE6-9 */ 446 | } 447 | 448 | .docker-btn-blue:enabled:active { 449 | 450 | .docker-btn; 451 | 452 | background: #00617f; /* Old browsers */ 453 | background: -moz-linear-gradient(top, #00617f 0%, #008bb8 100%); /* FF3.6+ */ 454 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#00617f), color-stop(100%,#008bb8)); /* Chrome,Safari4+ */ 455 | background: -webkit-linear-gradient(top, #00617f 0%,#008bb8 100%); /* Chrome10+,Safari5.1+ */ 456 | background: -o-linear-gradient(top, #00617f 0%,#008bb8 100%); /* Opera 11.10+ */ 457 | background: -ms-linear-gradient(top, #00617f 0%,#008bb8 100%); /* IE10+ */ 458 | background: linear-gradient(to bottom, #00617f 0%,#008bb8 100%); /* W3C */ 459 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00617f', endColorstr='#008bb8',GradientType=0 ); /* IE6-9 */ 460 | 461 | } 462 | 463 | .docker-btn-blue:disabled { 464 | color: white; 465 | 466 | background: #4f727c; /* Old browsers */ 467 | background: -moz-linear-gradient(top, #4f727c 0%, #89acb7 100%); /* FF3.6+ */ 468 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4f727c), color-stop(100%,#89acb7)); /* Chrome,Safari4+ */ 469 | background: -webkit-linear-gradient(top, #4f727c 0%,#89acb7 100%); /* Chrome10+,Safari5.1+ */ 470 | background: -o-linear-gradient(top, #4f727c 0%,#89acb7 100%); /* Opera 11.10+ */ 471 | background: -ms-linear-gradient(top, #4f727c 0%,#89acb7 100%); /* IE10+ */ 472 | background: linear-gradient(to bottom, #4f727c 0%,#89acb7 100%); /* W3C */ 473 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4f727c', endColorstr='#89acb7',GradientType=0 ); /* IE6-9 */ 474 | } 475 | 476 | #tutorialTop .closeButton { 477 | position: relative; 478 | float: right; 479 | margin: 6px; 480 | width: 18px; 481 | height: 18px; 482 | } 483 | 484 | #progress-indicator { 485 | margin-top: 20px; 486 | margin-right: 20px; 487 | float: right; 488 | // .debug; 489 | // width: 100px; 490 | // height: 100px; 491 | cursor: hand; 492 | } 493 | 494 | .progress-marker { 495 | 496 | position:relative; 497 | float: left; 498 | margin-right: 5px; 499 | cursor: hand; 500 | // top:5; 501 | // left:5; 502 | // z-index:1; 503 | // .debug; 504 | svg { 505 | width:30px; 506 | height:30px; 507 | cursor: pointer; 508 | } 509 | 510 | circle { fill: #eaeaea; } 511 | text { fill: black; } 512 | 513 | &.complete { 514 | circle { fill: green; } 515 | } 516 | 517 | &:hover { 518 | circle { fill: #01b0ee; } 519 | } 520 | 521 | &:active { 522 | circle { fill: #ff810a; } 523 | } 524 | 525 | &.active { 526 | circle { fill: #ff810a; } 527 | } 528 | } 529 | 530 | .complete { 531 | img { 532 | height: 45px; 533 | width: 45px; 534 | margin-top: 10px; 535 | margin-right: 10px; 536 | } 537 | 538 | ol { 539 | li { 540 | margin-bottom: 10px; 541 | font-weight: bold; 542 | } 543 | } 544 | } 545 | -------------------------------------------------------------------------------- /docker_tutorial/static/img/circle-green.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docker_tutorial/static/img/docker-letters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhrp/docker-tutorial/6edf3d18e6a71a22fbd7bd1a0f371fee924d615a/docker_tutorial/static/img/docker-letters.png -------------------------------------------------------------------------------- /docker_tutorial/static/img/docker-topicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhrp/docker-tutorial/6edf3d18e6a71a22fbd7bd1a0f371fee924d615a/docker_tutorial/static/img/docker-topicon.png -------------------------------------------------------------------------------- /docker_tutorial/static/img/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhrp/docker-tutorial/6edf3d18e6a71a22fbd7bd1a0f371fee924d615a/docker_tutorial/static/img/email.png -------------------------------------------------------------------------------- /docker_tutorial/static/img/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhrp/docker-tutorial/6edf3d18e6a71a22fbd7bd1a0f371fee924d615a/docker_tutorial/static/img/facebook.png -------------------------------------------------------------------------------- /docker_tutorial/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhrp/docker-tutorial/6edf3d18e6a71a22fbd7bd1a0f371fee924d615a/docker_tutorial/static/img/favicon.png -------------------------------------------------------------------------------- /docker_tutorial/static/img/fullscreen_exit_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhrp/docker-tutorial/6edf3d18e6a71a22fbd7bd1a0f371fee924d615a/docker_tutorial/static/img/fullscreen_exit_32x32.png -------------------------------------------------------------------------------- /docker_tutorial/static/img/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhrp/docker-tutorial/6edf3d18e6a71a22fbd7bd1a0f371fee924d615a/docker_tutorial/static/img/twitter.png -------------------------------------------------------------------------------- /docker_tutorial/static/js/steps.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | This is the main script file. It can attach to the terminal 3 | ### 4 | 5 | COMPLETE_URL = "/whats-next/" 6 | 7 | 8 | ### 9 | Array of question objects 10 | ### 11 | 12 | staticDockerPs = """ 13 | ID IMAGE COMMAND CREATED STATUS PORTS 14 | """ 15 | 16 | 17 | q = [] 18 | q.push ({ 19 | html: """ 20 |
There are actually two programs: The Docker daemon, which is a server process and which manages all the 22 | containers, and the Docker client, which acts as a remote control on the daemon. On most systems, like in this 23 | emulator, both execute on the same host.
24 | """ 25 | assignment: """ 26 |Check which Docker versions are running
28 |This will help you verify the daemon is running and you can connect to it. If you see which version is running 29 | you know you are all set.
30 | """ 31 | tip: "Try typing docker
to see the full list of accepted arguments
This emulator provides only a limited set of shell and Docker commands, so some commands may not work as expected
" 33 | command_expected: ['docker', 'version'] 34 | result: """Well done! Let's move to the next assignment.
""" 35 | }) 36 | 37 | q.push ({ 38 | html: """ 39 |The easiest way to get started is to use a container image from someone else. Container images are 41 | available on the Docker index, a central place to store images. You can find them online at 42 | index.docker.io 43 | and by using the commandline
44 | """ 45 | assignment: """ 46 |Use the commandline to search for an image called tutorial
48 | """ 49 | command_expected: ['docker', 'search', 'tutorial'] 50 | result: """You found it!
""" 51 | tip: "the format isdocker search <string>
"
52 | })
53 |
54 | q.push ({
55 | html: """
56 | Container images can be downloaded just as easily, using docker pull
.
For images from the central index, the name you specify is constructed as <username>/<repository>
59 |A group of special, trusted images such as the ubuntu base image can be retrieved by just their name <repository>.
60 | """ 61 | assignment: 62 | """ 63 |Please download the tutorial image you have just found
65 | """ 66 | command_expected: ['docker', 'pull', 'learn/tutorial'] 67 | result: """Cool. Look at the results. You'll see that Docker has downloaded a number of layers. In Docker all images (except the base image) are made up of several cumulative layers.
""" 68 | tip: """Don't forget to pull the full name of the repository e.g. 'learn/tutorial'
69 |Look under 'show expected command if you're stuck.
70 | """ 71 | }) 72 | 73 | 74 | q.push ({ 75 | html: """ 76 |You can think about containers as a process in a box. The box contains everything the process might need, so 78 | it has the filesystem, system libraries, shell and such, but by default none of it is started or run.
79 |
You 'start' a container by running a process in it. This process is the only process run, so when 80 | it completes the container is fully stopped. 81 | """ 82 | assignment: """ 83 |
Make our freshly loaded container image output "hello world"
85 |To do so you should run 'echo' in the container and have that say "hello world" 86 | 87 | """ 88 | command_expected: ["docker", "run", "learn/tutorial", "echo", "hello"] 89 | command_show: ["docker", "run", "learn/tutorial", 'echo "hello world"'] 90 | 91 | result: """
Great! Hellooooo World!
You have just started a container and executed a program inside of it, when 92 | the program stopped, so did the container.""" 93 | intermediateresults: [ 94 | () -> """
You seem to be almost there. Did you give the command `echo "hello world"` """, 95 | () -> """
You've got the arguments right. Did you get the command? Try /bin/bash ?
""" 96 | ] 97 | tip: """ 98 |The command docker run
takes a minimum of two arguments. An image name, and the command you want to execute
99 | within that image.
Check the expected command below if it does not work as expected
101 | """ 102 | }) 103 | 104 | q.push ({ 105 | html: """ 106 |Next we are going to install a simple program (ping) in the container. The image is based upon ubuntu, so you
108 | can run the command apt-get install -y ping
in the container.
Note that even though the container stops right after a command completes, the changes are not forgotten.
110 | """ 111 | assignment: """ 112 |Install 'ping' on top of the learn/tutorial image.
114 | """ 115 | command_expected: ["docker", "run", "learn/tutorial", "apt-get", "install", "-y", "ping"] 116 | result: """That worked! You have installed a program on top of a base image. Your changes to the filesystem have been 117 | kept, but are not yet saved.
""" 118 | intermediateresults: [ 119 | () -> """Not specifying -y on the apt-get install command will work for ping, because it has no other dependencies, but 120 | it will fail when apt-get wants to install dependencies. To get into the habit, please add -y after apt-get.
""", 121 | ] 122 | tip: """ 123 |Don't forget to use -y for noninteractive mode installation
124 |Not specifying -y on the apt-get install command will fail for most commands because it expects you to accept 125 | (y/n) but you cannot respond. 126 |
127 | """ 128 | }) 129 | 130 | q.push ({ 131 | html: """ 132 |After you make changes (by running a command inside a container), you probably want to save those changes. 134 | This will enable you to later start from this point onwards.
135 |With Docker, the process of saving the state is called committing. Commit basically saves the difference 136 | between the old image and the new state. The result is a new layer.
137 | """ 138 | assignment: """ 139 |First use docker ps -l
to find the ID of the container you created by installing ping.
Then save (commit) this container with the repository name 'learn/ping'
142 | """ 143 | command_expected: ["docker", "commit", "698", "learn/ping"] 144 | command_show: ["docker", "commit", "698", 'learn/ping'] 145 | result: """That worked! Please take note that Docker has returned a new ID. This id is the image id.
""" 146 | intermediateresults: [ () -> """You have not specified the correct repository name to commit to (learn/ping). This works, but giving your images a name 147 | makes them much easier to work with."""] 148 | tip: """docker commit
will show you the possible arguments.Now you have basically setup a complete, self contained environment with the 'ping' program installed.
160 |Your image can now be run on any host that runs Docker.
161 |Lets run this image on this machine.
162 | """ 163 | assignment: """ 164 |Run the ping program to ping www.google.com
166 | 167 | """ 168 | command_expected: ["docker", "run", 'learn/ping', 'ping', 'google.com' ] 169 | result: """That worked! Note that normally you can use Ctrl-C to disconnect. The container will keep running. This 170 | container will disconnect automatically.
""" 171 | intermediateresults: [ () -> """You have not specified a repository name. This is not wrong, but giving your images a name 172 | make them much easier to work with."""] 173 | tip: """You now have a running container. Let's see what is going on.
185 |Using docker ps
we can see a list of all running containers, and using docker inspect
186 | we can see all sorts of useful information about this container.
Find the container id of the running container, and then inspect the container using docker inspect.
191 | 192 | """ 193 | command_expected: ["docker", "inspect", "efe" ] 194 | result: """Success! Have a look at the output. You can see the ip-address, status and other information.
""" 195 | intermediateresults: [ () -> """You have not specified a repository name. This is not wrong, but giving your images a name 196 | make them much easier to work with."""] 197 | tip: """Now you have verified that your application container works, you can share it.
214 |Remember you pulled (downloaded) the learn/tutorial image from the index? You can also share your built images 215 | to the index by pushing (uploading) them to there. That way you can easily retrieve them for re-use and share them 216 | with others.
217 | """ 218 | assignment: """ 219 |Push your container image learn/ping to the index
221 | 222 | """ 223 | #command_expected: ["docker", "push", "learn/ping"] 224 | command_expected: ["will_never_be_valid"] 225 | command_show: ["docker", "push", "learn/ping"] 226 | result: """""" 227 | intermediateresults: 228 | [ 229 | () -> 230 | $('#instructions .assignment').hide() 231 | $('#tips, #command').hide() 232 | 233 | $('#instructions .text').html(""" 234 |You have mastered the basic docker commands!
237 |Did you enjoy this tutorial? Share it!
238 | 243 |- Or -
250 |Continue to learn about the way to automatically build your containers from a file.
251 | 252 |All done!. You are now pushing a container image to the index. You can see that push, just like pull, happens layer by layer.
""" 260 | ] 261 | tip: """docker images
will show you which images are currently on your hostdocker push
is the command to push imagesThere are actually two programs: The Docker daemon, which is a server process and which manages all the\ncontainers, and the Docker client, which acts as a remote control on the daemon. On most systems, like in this\nemulator, both execute on the same host.
", 23 | assignment: "Check which Docker versions are running
\nThis will help you verify the daemon is running and you can connect to it. If you see which version is running\nyou know you are all set.
", 24 | tip: "Try typing docker
to see the full list of accepted arguments
This emulator provides only a limited set of shell and Docker commands, so some commands may not work as expected
", 25 | command_expected: ['docker', 'version'], 26 | result: "Well done! Let's move to the next assignment.
" 27 | }); 28 | 29 | q.push({ 30 | html: "The easiest way to get started is to use a container image from someone else. Container images are\navailable on the Docker index, a central place to store images. You can find them online at\nindex.docker.io\nand by using the commandline
", 31 | assignment: "Use the commandline to search for an image called tutorial
", 32 | command_expected: ['docker', 'search', 'tutorial'], 33 | result: "You found it!
", 34 | tip: "the format isdocker search <string>
"
35 | });
36 |
37 | q.push({
38 | html: "Container images can be downloaded just as easily, using docker pull
.
For images from the central index, the name you specify is constructed as <username>/<repository>
\nA group of special, trusted images such as the ubuntu base image can be retrieved by just their name <repository>.
", 39 | assignment: "Please download the tutorial image you have just found
", 40 | command_expected: ['docker', 'pull', 'learn/tutorial'], 41 | result: "Cool. Look at the results. You'll see that Docker has downloaded a number of layers. In Docker all images (except the base image) are made up of several cumulative layers.
", 42 | tip: "Don't forget to pull the full name of the repository e.g. 'learn/tutorial'
\nLook under 'show expected command if you're stuck.
" 43 | }); 44 | 45 | q.push({ 46 | html: "You can think about containers as a process in a box. The box contains everything the process might need, so\nit has the filesystem, system libraries, shell and such, but by default none of it is started or run.
\n
You 'start' a container by running a process in it. This process is the only process run, so when\nit completes the container is fully stopped.", 47 | assignment: "
Make our freshly loaded container image output \"hello world\"
\nTo do so you should run 'echo' in the container and have that say \"hello world\"\n", 48 | command_expected: ["docker", "run", "learn/tutorial", "echo", "hello"], 49 | command_show: ["docker", "run", "learn/tutorial", 'echo "hello world"'], 50 | result: "
Great! Hellooooo World!
You have just started a container and executed a program inside of it, when\nthe program stopped, so did the container.", 51 | intermediateresults: [ 52 | function() { 53 | return "
You seem to be almost there. Did you give the command `echo \"hello world\"` "; 54 | }, function() { 55 | return "
You've got the arguments right. Did you get the command? Try /bin/bash ?
"; 56 | } 57 | ], 58 | tip: "The command docker run
takes a minimum of two arguments. An image name, and the command you want to execute\nwithin that image.
Check the expected command below if it does not work as expected
" 59 | }); 60 | 61 | q.push({ 62 | html: "Next we are going to install a simple program (ping) in the container. The image is based upon ubuntu, so you\ncan run the command apt-get install -y ping
in the container.
Note that even though the container stops right after a command completes, the changes are not forgotten.
", 63 | assignment: "Install 'ping' on top of the learn/tutorial image.
", 64 | command_expected: ["docker", "run", "learn/tutorial", "apt-get", "install", "-y", "ping"], 65 | result: "That worked! You have installed a program on top of a base image. Your changes to the filesystem have been\nkept, but are not yet saved.
", 66 | intermediateresults: [ 67 | function() { 68 | return "Not specifying -y on the apt-get install command will work for ping, because it has no other dependencies, but\nit will fail when apt-get wants to install dependencies. To get into the habit, please add -y after apt-get.
"; 69 | } 70 | ], 71 | tip: "Don't forget to use -y for noninteractive mode installation
\nNot specifying -y on the apt-get install command will fail for most commands because it expects you to accept\n(y/n) but you cannot respond.\n
" 72 | }); 73 | 74 | q.push({ 75 | html: "After you make changes (by running a command inside a container), you probably want to save those changes.\nThis will enable you to later start from this point onwards.
\nWith Docker, the process of saving the state is called committing. Commit basically saves the difference\nbetween the old image and the new state. The result is a new layer.
", 76 | assignment: "First use docker ps -l
to find the ID of the container you created by installing ping.
Then save (commit) this container with the repository name 'learn/ping'
", 77 | command_expected: ["docker", "commit", "698", "learn/ping"], 78 | command_show: ["docker", "commit", "698", 'learn/ping'], 79 | result: "That worked! Please take note that Docker has returned a new ID. This id is the image id.
", 80 | intermediateresults: [ 81 | function() { 82 | return "You have not specified the correct repository name to commit to (learn/ping). This works, but giving your images a name\nmakes them much easier to work with."; 83 | } 84 | ], 85 | tip: "docker commit
will show you the possible arguments.Now you have basically setup a complete, self contained environment with the 'ping' program installed.
\nYour image can now be run on any host that runs Docker.
\nLets run this image on this machine.
", 90 | assignment: "Run the ping program to ping www.google.com
\n", 91 | command_expected: ["docker", "run", 'learn/ping', 'ping', 'google.com'], 92 | result: "That worked! Note that normally you can use Ctrl-C to disconnect. The container will keep running. This\ncontainer will disconnect automatically.
", 93 | intermediateresults: [ 94 | function() { 95 | return "You have not specified a repository name. This is not wrong, but giving your images a name\nmake them much easier to work with."; 96 | } 97 | ], 98 | tip: "You now have a running container. Let's see what is going on.
\nUsing docker ps
we can see a list of all running containers, and using docker inspect
\nwe can see all sorts of useful information about this container.
Find the container id of the running container, and then inspect the container using docker inspect.
\n", 104 | command_expected: ["docker", "inspect", "efe"], 105 | result: "Success! Have a look at the output. You can see the ip-address, status and other information.
", 106 | intermediateresults: [ 107 | function() { 108 | return "You have not specified a repository name. This is not wrong, but giving your images a name\nmake them much easier to work with."; 109 | } 110 | ], 111 | tip: "Now you have verified that your application container works, you can share it.
\nRemember you pulled (downloaded) the learn/tutorial image from the index? You can also share your built images\nto the index by pushing (uploading) them to there. That way you can easily retrieve them for re-use and share them\nwith others.
", 117 | assignment: "Push your container image learn/ping to the index
\n", 118 | command_expected: ["will_never_be_valid"], 119 | command_show: ["docker", "push", "learn/ping"], 120 | result: "", 121 | intermediateresults: [ 122 | function() { 123 | var data; 124 | 125 | $('#instructions .assignment').hide(); 126 | $('#tips, #command').hide(); 127 | $('#instructions .text').html("You have mastered the basic docker commands!
\nDid you enjoy this tutorial? Share it!
\n \n- Or -
\nContinue to learn about the way to automatically build your containers from a file.
\n\nAll done!. You are now pushing a container image to the index. You can see that push, just like pull, happens layer by layer.
"; 133 | } 134 | ], 135 | tip: "docker images
will show you which images are currently on your hostdocker push
is the command to push images
21 | As we showed in the introductory tutorial, containers can be built manually.
22 | However, with a Dockerfile, you can describe build steps once and then build a container automatically--any time you want--from source.
23 |
25 | Dockerfiles can be viewed as an image representation. They provide a simple syntax for building images and they are a great way to automate and script the images creation. 26 | If you are really serious about Docker, you should master the Dockerfile syntax. Let’s start! 27 |
28 |
29 | If you haven't taken the introductory tutorial, you should do so before starting this.
30 |
31 |
29 | In this section we will cover the most important instructions: FROM
and RUN
.
30 | We will also see how to use the docker build
command and how to specify the maintainer of the Dockerfile and how to insert comments.
31 |
35 | All Dockerfile instructions look like this: 36 |
37 | 38 | 39 |INSTRUCTION arguments40 | 41 | 42 |
43 | Instructions are not case sensitive, but we recommend to use capital letters. 44 |
45 | 46 |
49 | The first instruction of every Dockerfile needs to be the FROM
instruction
50 |
FROM image53 | 54 |
55 | It sets the base image for subsequent instructions.
56 | Example:
57 |
FROM ubuntu59 |
60 | In this example, we chose ubuntu as our base image. ubuntu is a “General use Ubuntu base image” provided by the Docker team.
61 | You can find a list of available images from the command line by using the docker search command.
62 | Example:
63 |
65 | root@precise64:/home/vagrant/dockerfiles# docker search centos
66 | Found 14 results matching your query ("centos")
67 | NAME DESCRIPTION
68 | centos
69 | backjlack/centos-6.4-x86_64
70 | creack/centos
71 | mbkan/lamp centos with ssh, LAMP, PHPMyAdmin(root password 'toor' and MySQL root password 'sysadmin'). Auto starts sshd, MySQL and Apache. Just launch us...
72 | zenosslabs/centos
73 | slantview/centos-chef-solo CentOS 6.4 with chef-solo.
74 | tchap/centos-epel The official centos image having EPEL added among its repositories.
75 | backjlack/centos This repository contains the following images: Centos 5.9 (more to be added soon)
76 | oss17888/centos6.4
77 | markl/centos
78 | troygoode/centos-node-hello
79 | austin/centos-base
80 | estrai/centos32 Centos 5.9 32-bit based on an OpenVZ image.
81 | comodit/centos
82 |
83 | 84 | So let’s say you want to use centos as you base image, you can for instance use the following line:
85 |86 | FROM centos 87 |88 |
89 | You can also browse available images from Docker and the community online: index.docker.io 90 |
91 |
93 | The RUN
instruction will execute any commands on the current image and commit the results. The resulting committed image will be used for the next step in the Dockerfile.
94 | Layering RUN instructions and generating commits conforms to the core concepts of Docker where commits are cheap and containers can be created from any point in an image’s history, much like source control.
96 | RUN command 97 |98 |
99 | RUN command
is equivalent to the docker commands: docker run image command
+ docker commit container_id
,
100 | Where image would be replaced automatically with the current image, and container_id would be the result of the previous RUN
instruction.
101 | Example:
103 | RUN apt-get install -y memcached 104 |105 | 106 | As we saw earlier, we need to have the
FROM
instruction before any RUN
instruction, so that docker knows from which base image to start running the instruction on. For instance:
107 |
108 | 109 | FROM ubuntu 110 | RUN apt-get install -y memcached 111 |112 |
113 | This example creates an image with memcached installed on the ubuntu image. It is the equivalent to the following command lines: 114 |
115 |116 | root@precise64:/home/vagrant/dockerfiles# docker run ubuntu apt-get install -y memcached 117 | […] 118 | [=> container id is 9fb69e798e67] 119 | root@precise64:/home/vagrant/dockerfiles# docker commit 9fb69e798e67 120 | 6742949a1bae 121 |122 | 123 |
125 | Once you have your Dockerfile ready, you can use the docker build command to create your image from it. There are different ways to use this command: 126 |
docker build .
129 | docker build - < Dockerfile
132 | docker build github.com/creack/docker-firefox
139 | Let’s run our last Dockerfile example. 140 |
141 |142 | root@precise64:/home/vagrant/dockerfiles# cat Dockerfile 143 | FROM ubuntu 144 | RUN apt-get install -y memcached 145 | root@precise64:/home/vagrant/dockerfiles# docker build . 146 | Uploading context 20480 bytes 147 | Step 1 : FROM ubuntu 148 | Pulling repository ubuntu 149 | Pulling image 8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c (precise) from ubuntu 150 | Pulling image b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc (quantal) from ubuntu 151 | Pulling b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc metadata 152 | Pulling b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc fs layer 153 | Downloading 10.24 kB/10.24 kB (100%) 154 | Pulling 27cf784147099545 metadata 155 | Pulling 27cf784147099545 fs layer 156 | Downloading 10.24 kB/10.24 kB (100%) 157 | Pulling 27cf784147099545 metadata 158 | Pulling 27cf784147099545 fs layer 159 | Downloading 94.86 MB/94.86 MB (100%) 160 | ---> 8dbd9e392a96 161 | Step 2 : RUN apt-get install -y memcached 162 | ---> Running in dedc7af62d9f 163 | Reading package lists... 164 | Building dependency tree... 165 | The following extra packages will be installed: 166 | libclass-isa-perl libevent-2.0-5 libgdbm3 libswitch-perl netbase perl 167 | perl-modules 168 | Suggested packages: 169 | libcache-memcached-perl libmemcached perl-doc libterm-readline-gnu-perl 170 | libterm-readline-perl-perl make libpod-plainer-perl 171 | The following NEW packages will be installed: 172 | libclass-isa-perl libevent-2.0-5 libgdbm3 libswitch-perl memcached netbase 173 | perl perl-modules 174 | 0 upgraded, 8 newly installed, 0 to remove and 0 not upgraded. 175 | Need to get 8070 kB of archives. 176 | After this operation, 32.8 MB of additional disk space will be used. 177 | Get:1 http://archive.ubuntu.com/ubuntu/ precise/main libgdbm3 amd64 1.8.3-10 [35.3 kB] 178 | Get:2 http://archive.ubuntu.com/ubuntu/ precise/main netbase all 4.47ubuntu1 [15.0 kB] 179 | Get:3 http://archive.ubuntu.com/ubuntu/ precise/main libclass-isa-perl all 0.36-3 [11.9 kB] 180 | Get:4 http://archive.ubuntu.com/ubuntu/ precise/main libevent-2.0-5 amd64 2.0.16-stable-1 [127 kB] 181 | Get:5 http://archive.ubuntu.com/ubuntu/ precise/main perl-modules all 5.14.2-6ubuntu2 [3369 kB] 182 | Get:6 http://archive.ubuntu.com/ubuntu/ precise/main perl amd64 5.14.2-6ubuntu2 [4417 kB] 183 | Get:7 http://archive.ubuntu.com/ubuntu/ precise/main libswitch-perl all 2.16-2 [19.2 kB] 184 | Get:8 http://archive.ubuntu.com/ubuntu/ precise/main memcached amd64 1.4.13-0ubuntu2 [75.3 kB] 185 | debconf: delaying package configuration, since apt-utils is not installed 186 | Fetched 8070 kB in 7s (1136 kB/s) 187 | Selecting previously unselected package libgdbm3. 188 | (Reading database ... 7545 files and directories currently installed.) 189 | Unpacking libgdbm3 (from .../libgdbm3_1.8.3-10_amd64.deb) ... 190 | Selecting previously unselected package netbase. 191 | Unpacking netbase (from .../netbase_4.47ubuntu1_all.deb) ... 192 | Selecting previously unselected package libclass-isa-perl. 193 | Unpacking libclass-isa-perl (from .../libclass-isa-perl_0.36-3_all.deb) ... 194 | Selecting previously unselected package libevent-2.0-5. 195 | Unpacking libevent-2.0-5 (from .../libevent-2.0-5_2.0.16-stable-1_amd64.deb) ... 196 | Selecting previously unselected package perl-modules. 197 | Unpacking perl-modules (from .../perl-modules_5.14.2-6ubuntu2_all.deb) ... 198 | Selecting previously unselected package perl. 199 | Unpacking perl (from .../perl_5.14.2-6ubuntu2_amd64.deb) ... 200 | Selecting previously unselected package libswitch-perl. 201 | Unpacking libswitch-perl (from .../libswitch-perl_2.16-2_all.deb) ... 202 | Selecting previously unselected package memcached. 203 | Unpacking memcached (from .../memcached_1.4.13-0ubuntu2_amd64.deb) ... 204 | Setting up libgdbm3 (1.8.3-10) ... 205 | Setting up netbase (4.47ubuntu1) ... 206 | Setting up libclass-isa-perl (0.36-3) ... 207 | Setting up libevent-2.0-5 (2.0.16-stable-1) ... 208 | Setting up perl-modules (5.14.2-6ubuntu2) ... 209 | Setting up perl (5.14.2-6ubuntu2) ... 210 | update-alternatives: using /usr/bin/prename to provide /usr/bin/rename (rename) in auto mode. 211 | Setting up memcached (1.4.13-0ubuntu2) ... 212 | Starting memcached: memcached. 213 | Setting up libswitch-perl (2.16-2) ... 214 | Processing triggers for libc-bin ... 215 | ldconfig deferred processing now taking place 216 | ---> 1dcfa24c8ca6 217 | Successfully built 1dcfa24c8ca6 218 |219 |
220 | You can see your newly created image 1dcfa24c8ca6
in the ouput of docker images
:
221 |
223 | root@precise64:/home/vagrant/dockerfiles# docker images
224 | REPOSITORY TAG ID CREATED SIZE
225 | ubuntu 12.04 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
226 | ubuntu 12.10 b750fe79269d 4 months ago 24.65 kB (virtual 180.1 MB)
227 | ubuntu latest 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
228 | ubuntu precise 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
229 | ubuntu quantal b750fe79269d 4 months ago 24.65 kB (virtual 180.1 MB)
230 | <none> <none> 1275893fed44 9 minutes ago 12.3 kB (virtual 566.4 MB)
231 | <none> <none> 1dcfa24c8ca6 About a minute ago 52.27 MB (virtual 183.8 MB)
232 |
233 |
234 | Note that it does not have a name yet. Let’s name it with the command docker tag
.
235 |
237 | root@precise64:/home/vagrant/dockerfiles# docker tag 1dcfa24c8ca6 memcached 238 | root@precise64:/home/vagrant/dockerfiles# docker images 239 | REPOSITORY TAG ID CREATED SIZE 240 | ubuntu 12.04 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB) 241 | ubuntu 12.10 b750fe79269d 4 months ago 24.65 kB (virtual 180.1 MB) 242 | ubuntu latest 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB) 243 | ubuntu precise 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB) 244 | ubuntu quantal b750fe79269d 4 months ago 24.65 kB (virtual 180.1 MB) 245 | memcached latest 1dcfa24c8ca6 4 minutes ago 52.27 MB (virtual 183.8 MB) 246 | <none> <none> 1275893fed44 12 minutes ago 12.3 kB (virtual 566.4 MB) 247 |248 |
249 | The good news is that docker build
comes with the option -t
with which you can directly tag the (last) image created with the Dockerfile. So we could have used docker build -t memcached .
directly.
250 |
252 | root@precise64:/home/vagrant/dockerfiles# docker build -t memcached_new . 253 | Uploading context 20480 bytes 254 | Step 1 : FROM ubuntu 255 | […] 256 | Step 2 : RUN apt-get install -y memcached 257 | […] 258 | Successfully built 1dcfa24c8ca6 259 | root@precise64:/home/vagrant/dockerfiles# docker images 260 | REPOSITORY TAG ID CREATED SIZE 261 | ubuntu 12.04 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB) 262 | ubuntu 12.10 b750fe79269d 4 months ago 24.65 kB (virtual 180.1 MB) 263 | ubuntu latest 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB) 264 | ubuntu precise 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB) 265 | ubuntu quantal b750fe79269d 4 months ago 24.65 kB (virtual 180.1 MB) 266 | memcached_new latest 1dcfa24c8ca6 4 minutes ago 52.27 MB (virtual 183.8 MB) 267 | <none> <none> 1275893fed44 4 minutes ago 12.3 kB (virtual 566.4 MB) 268 |269 | 270 |
272 | We have written almost all of it in the previous sections. 273 |
274 |275 | FROM ubuntu 276 | RUN apt-get install -y memcached 277 |278 |
279 | But we want to ensure that the ubuntu package repository is up to date. With the command line we would do:
280 |281 | root@precise64:/home/vagrant/dockerfiles# echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 282 | root@precise64:/home/vagrant/dockerfiles# apt-get update 283 |284 |
285 | Within the Dockerfile, we will use the RUN
instruction.
286 |
288 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 289 | RUN apt-get update 290 |291 |
292 | So our final Dockerfile looks like this: 293 |
294 |295 | FROM ubuntu 296 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 297 | RUN apt-get update 298 | RUN apt-get install -y memcached 299 |300 |
301 | Note that this Dockerfile would be equivalent to the following command lines:
302 |303 | root@precise64:/home/vagrant/dockerfiles# docker run ubuntu echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 304 | […] 305 | [=> container id is 72d2a39bc64d] 306 | root@precise64:/home/vagrant/dockerfiles# docker commit 72d2a39bc64d 307 | d7a97544c0c7 308 | root@precise64:/home/vagrant/dockerfiles# docker run d7a97544c0c7 apt-get update 309 | […] 310 | [=> container id is 66b22a0ef7c9] 311 | root@precise64:/home/vagrant/dockerfiles# docker commit 66b22a0ef7c9 312 | a5f142a604b3 313 | root@precise64:/home/vagrant/dockerfiles# docker run a5f142a604b3 apt-get install -y memcached 314 | […] 315 | [=> container id is 9fb69e798e67] 316 | root@precise64:/home/vagrant/dockerfiles# docker commit 9fb69e798e67 317 | 6742949a1bae 318 |319 |
320 | Let’s create our image. We will use the -t
option with docker build
in order to give a name to our image.
321 |
323 | root@precise64:/home/vagrant/dockerfiles# cat Dockerfile 324 | FROM ubuntu 325 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 326 | RUN apt-get update 327 | RUN apt-get install -y memcached 328 | root@precise64:/home/vagrant/dockerfiles# docker build -t brand_new_memcached - < Dockerfile 329 | Uploading context 2048 bytes 330 | Step 1 : FROM ubuntu 331 | ---> 8dbd9e392a96 332 | Step 2 : RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 333 | ---> Using cache 334 | ---> 913c2344b312 335 | Step 3 : RUN apt-get update 336 | ---> Using cache 337 | ---> 0a77b6e48393 338 | Step 4 : RUN apt-get install -y memcached 339 | ---> Running in d1eadc19edc6 340 | Reading package lists... 341 | Building dependency tree... 342 | The following extra packages will be installed: 343 | libclass-isa-perl libevent-2.0-5 libgdbm3 libswitch-perl netbase perl 344 | perl-modules 345 | […] 346 | Starting memcached: memcached. 347 | Setting up libswitch-perl (2.16-2) ... 348 | Processing triggers for libc-bin ... 349 | ldconfig deferred processing now taking place 350 | ---> 782a37534312 351 | Successfully built 782a37534312 352 |353 |
354 | The id of the image is 782a37534312 but it already has a name as we can verify with docker images: 355 |
356 |
357 | root@precise64:/home/vagrant/dockerfiles# docker images
358 | REPOSITORY TAG ID CREATED SIZE
359 | memcached_new latest 1dcfa24c8ca6 12 minutes ago 52.27 MB (virtual 183.8 MB)
360 | brand_new_memcached latest 782a37534312 54 seconds ago 74.7 MB (virtual 350.7 MB)
361 | ubuntu 12.04 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
362 | ubuntu 12.10 b750fe79269d 4 months ago 24.65 kB (virtual 180.1 MB)
363 | ubuntu latest 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
364 | ubuntu precise 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
365 | ubuntu quantal b750fe79269d 4 months ago 24.65 kB (virtual 180.1 MB)
366 | memcached latest 1dcfa24c8ca6 12 minutes ago 52.27 MB (virtual 183.8 MB)
367 | <none> <none> 1275893fed44 21 minutes ago 12.3 kB (virtual 566.4 MB)
368 |
369 | 370 | Congratulations! You just created your first image with a Dockerfile :)
371 |372 | You can verify that memcached is installed in your image by running these commands:
373 |374 | root@precise64:/home/vagrant/dockerfiles# docker run -i -t brand_new_memcached /bin/bash 375 | root@c28a2c4be825:/# memcached 376 | can't run as root without the -u switch 377 |378 | 379 |
381 | Code should always be commented. The same rule applies to the Dockerfile.
382 | A character #
at the beginning of a line defines a comment. The comments are not exectuted. Comments are not mandatory, but we recommend using them to:
383 |
391 | # Memcached 392 | # 393 | # VERSION 1.0 394 | 395 | # use the ubuntu base image provided by dotCloud 396 | FROM ubuntu 397 | 398 | # make sure the package repository is up to date 399 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 400 | RUN apt-get update 401 | 402 | # install memcached 403 | RUN apt-get install -y memcached 404 |405 | 406 |
408 | As you could expect, the MAINTAINER
instruction is used to specify name and contact information of the maintainer of the Dockerfile.
409 | Example:
410 |
412 | MAINTAINER Guillaume J. Charmes, guillaume@dotcloud.com 413 |414 |
415 | The final version of the Dockerfile becomes
416 |417 | # Memcached 418 | # 419 | # VERSION 1.0 420 | 421 | # use the ubuntu base image provided by dotCloud 422 | FROM ubuntu 423 | MAINTAINER Guillaume J. Charmes, guillaume@dotcloud.com 424 | # make sure the package repository is up to date 425 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 426 | RUN apt-get update 427 | 428 | # install memcached 429 | RUN apt-get install -y memcached 430 |431 | 432 |
460 | Your best friend Eric Bardin sent you a Dockerfile, but some parts were lost in the ocean. Can you find the missing parts? 461 |
462 |464 | # This is a Dockerfile to create an image with Memcached and Emacs installed. 465 | # 466 | # VERSION 1.0 467 | 468 | # use the ubuntu base image provided by dotCloud 469 | ub 470 | 471 | E B, eric.bardin@dotcloud.com 472 | 473 | # make sure the package repository is up to date 474 | echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 475 | apt-get update 476 | 477 | # install memcached 478 | RUN apt-get install -y 479 | 480 | # install emacs 481 | apt-get install -y emacs23 482 | 483 |484 |
In the next level, we will go into more detail about how to specify which command should be executed when the container starts, 495 | which user to use, and how expose a particular port.
496 | 497 | Go to the next level 498 | 499 | 500 |
30 | In this section we will cover three important instructions: ENTRYPOINT
, USER
and EXPOSE
. These commands will help you write more advanced and therefore more complex containers.
31 | At the end of this tutorial you will be able to understand and write this Dockerfile:
32 |
34 | # Memcached 35 | # 36 | # VERSION 42 37 | 38 | # use the ubuntu base image provided by dotCloud 39 | FROM ubuntu 40 | 41 | MAINTAINER Victor Coisne victor.coisne@dotcloud.com 42 | 43 | # make sure the package repository is up to date 44 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 45 | RUN apt-get update 46 | 47 | # install memcached 48 | RUN apt-get install -y memcached 49 | 50 | # Launch memcached when launching the container 51 | ENTRYPOINT ["memcached"] 52 | 53 | # run memcached as the daemon user 54 | USER daemon 55 | 56 | # expose memcached port 57 | EXPOSE 11211 58 |59 |
60 | Ready? Go!
61 | 62 |
64 | Before taking this tutorial, please make sure that you completed and understand all instructions covered by the Level 1 Dockerfile tutorial (FROM
, RUN
, MAINTAINER
, #
). If this is not the case please start with Level 1.
65 |
The ENTRYPOINT
instruction permits you to trigger a command as soon as the container starts.
74 | Let’s say you want your container to execute echo “Whale You Be My Container?” as soon as it starts. Then you will use: 75 |
76 |77 | ENTRYPOINT echo “Whale You Be My Container?” 78 |79 |
80 | Let’s try it! 81 |
82 |83 | root@precise64:/home/vagrant/dockerfiles/tutorial1# cat Dockerfile_whale_you_be_my_container 84 | # Whale you be my container? 85 | # 86 | # VERSION 0.42 87 | 88 | # use the ubuntu base image provided by dotCloud 89 | FROM ubuntu 90 | 91 | MAINTAINER Victor Coisne victor.coisne@dotcloud.com 92 | 93 | # say hello when the container is launched 94 | ENTRYPOINT echo "Whale you be my container" 95 | root@precise64:/home/vagrant/dockerfiles/tutorial1# docker build -t whaleyoubemycontainer - < Dockerfile_whale_you_be_my_container 96 | Uploading context 2048 bytes 97 | Step 1 : FROM ubuntu 98 | Pulling repository ubuntu 99 | ---> b750fe79269dlayersantl) from ubuntu, endpoint: https://cdn-registry-1.docker.io/v1/ 100 | Step 2 : MAINTAINER Victor Coisne victor.coisne@dotcloud.com 101 | ---> Running in 2fd00b89982b 102 | ---> a2163a4bb4e6 103 | Step 3 : ENTRYPOINT echo "Whale you be my container" 104 | ---> Running in 81f46dafd81a 105 | ---> f62c1b30adad 106 | Successfully built f62c1b30adad 107 | root@precise64:/home/vagrant/dockerfiles/tutorial1# docker run whaleyoubemycontainer 108 | Whale you be my container 109 | root@precise64:/home/vagrant/dockerfiles/tutorial1# 110 |111 |
112 | There are two syntaxes for the ENTRYPOINT
instruction. Instead of command param1 param2 … you could also use the exec format:
113 |
115 | ENTRYPOINT ["echo", "Whale you be my container"] 116 |117 |
118 | This is actually the preferred form, so we will use this form from now on. 119 |
120 |
124 | The ENTRYPOINT
instruction helps to configure a container that you can run as an executable. That is, when you specify an ENTRYPOINT
, the whole container runs as if it was just that executable. Let’s see an example with a a container running wc –l
as ENTRYPOINT
125 |
127 | root@precise64:/home/vagrant/dockerfiles/tutorial1# cat Dockerfile_wc 128 | # This is wc 129 | # 130 | # VERSION 0.42 131 | 132 | # use the ubuntu base image provided by dotCloud 133 | FROM ubuntu 134 | 135 | MAINTAINER Victor Coisne victor.coisne@dotcloud.com 136 | 137 | # count lines with wc 138 | ENTRYPOINT ["wc", "-l"] 139 | root@precise64:/home/vagrant/dockerfiles/tutorial1# docker build -t wc - < Dockerfile_wc 140 | Uploading context 2048 bytes 141 | Step 1 : FROM ubuntu 142 | ---> b750fe79269d 143 | Step 2 : MAINTAINER Victor Coisne victor.coisne@dotcloud.com 144 | ---> Using cache 145 | ---> a2163a4bb4e6 146 | Step 3 : ENTRYPOINT ["wc", "-l"] 147 | ---> Using cache 148 | ---> 52504f229d30 149 | Successfully built 52504f229d30 150 | root@precise64:/home/vagrant/dockerfiles/tutorial1# cat /etc/passwd | docker run -i wc 151 | 28 152 |153 |
157 | Remember our Dockerfile from Level 1? We can now add an ENTRYPOINT. Memcached needs to be run by the user daemon. So the ENTRYPOINT instruction will look like this: 158 |
159 |ENTRYPOINT ["memcached", "-u", "daemon"]
160 | 161 | This is the resulting Dockerfile 162 |
163 |164 | # Memcached 165 | # 166 | # VERSION 2.0 167 | 168 | # use the ubuntu base image provided by dotCloud 169 | FROM ubuntu 170 | MAINTAINER Guillaume J. Charmes guillaume@dotcloud.com 171 | # make sure the package repository is up to date 172 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 173 | RUN apt-get update 174 | 175 | # install memcached 176 | RUN apt-get install -y memcached 177 | 178 | # Launch memcached when launching the container 179 | ENTRYPOINT ["memcached", "-u", "daemon"] 180 |181 |
185 | As we have seen earlier with Memcached, you may need to run the ENTRYPOINT
commands as a different user than root. In the previous example, Memcached already has the option to run as another user. But it is not the case for all services/programs. Here comes the USER
instruction. As you may have already understood, the USER
instruction sets the username or UID to use when running the image.
186 | With this new instruction, instead of this
188 | ENTRYPOINT ["memcached", "-u", "daemon"] 189 |190 |
191 | We can now use this 192 |
193 |194 | ENTRYPOINT ["memcached"] 195 | USER daemon 196 |197 |
201 | Here is our updated Memcached Dockerfile 202 |
203 |204 | # Memcached 205 | # 206 | # VERSION 2.1 207 | 208 | # use the ubuntu base image provided by dotCloud 209 | FROM ubuntu 210 | 211 | MAINTAINER Victor Coisne victor.coisne@dotcloud.com 212 | 213 | # make sure the package repository is up to date 214 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 215 | RUN apt-get update 216 | 217 | # install memcached 218 | RUN apt-get install -y memcached 219 | 220 | # Launch memcached when launching the container 221 | ENTRYPOINT ["memcached"] 222 | 223 | # run memcached as the daemon user 224 | USER daemon 225 |226 |
230 | The EXPOSE
instruction sets ports to be exposed when running the image.
231 |
236 | Memcached uses port 11211
, so we have to expose this port in order to be able to talk to the Memcached container from outside the container. This is simply done with this line
237 |
239 | EXPOSE 11211 240 |241 |
242 | By doing so, the container will sets the port 11211
to be exposed when running the Memcached image.
243 | The final Dockerfile now looks like this:
244 |
246 | # Memcached 247 | # 248 | # VERSION 2.2 249 | 250 | # use the ubuntu base image provided by dotCloud 251 | FROM ubuntu 252 | 253 | MAINTAINER Victor Coisne victor.coisne@dotcloud.com 254 | 255 | # make sure the package repository is up to date 256 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 257 | RUN apt-get update 258 | 259 | # install memcached 260 | RUN apt-get install -y memcached 261 | 262 | # Launch memcached when launching the container 263 | ENTRYPOINT ["memcached"] 264 | 265 | # run memcached as the daemon user 266 | USER daemon 267 | 268 | # expose memcached port 269 | EXPOSE 11211 270 |271 | 272 |
275 | So, we are on a server and we need Memcached. Let's use what we have just learned :) 276 |
277 |281 | First, let’s build the image, from the Dockerfile. 282 |
283 |284 | root@precise64:/home/vagrant/dockerfiles/tutorial1# cat Dockerfile 285 | # Memcached 286 | # 287 | # VERSION 2.2 288 | 289 | # use the ubuntu base image provided by dotCloud 290 | FROM ubuntu 291 | 292 | MAINTAINER Victor Coisne victor.coisne@dotcloud.com 293 | 294 | # make sure the package repository is up to date 295 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 296 | RUN apt-get update 297 | 298 | # install memcached 299 | RUN apt-get install -y memcached 300 | 301 | # Launch memcached when launching the container 302 | ENTRYPOINT ["memcached"] 303 | 304 | # run memcached as the daemon user 305 | USER daemon 306 | 307 | # expose memcached port 308 | EXPOSE 11211 309 | root@precise64:/home/vagrant/dockerfiles/tutorial1# docker build -t memcached - < Dockerfile 310 | Uploading context 2560 bytes 311 | Step 1 : FROM ubuntu 312 | ---> 8dbd9e392a96 313 | Step 2 : MAINTAINER Victor Coisne victor.coisne@dotcloud.com 314 | ---> Using cache 315 | ---> dee832c11d42 316 | Step 3 : RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 317 | ---> Using cache 318 | ---> 66678b24bab4 319 | Step 4 : RUN apt-get update 320 | ---> Using cache 321 | ---> 2fbfe4f542d8 322 | Step 5 : RUN apt-get install -y memcached 323 | ---> Using cache 324 | ---> 80c7e70e69a1 325 | Step 6 : ENTRYPOINT ["memcached"] 326 | ---> Running in df59a42eeaa7 327 | ---> 090512aab2ac 328 | Step 7 : USER daemon 329 | ---> Running in f4618f8c72ec 330 | ---> 264b9e444135 331 | Step 8 : EXPOSE 11211 332 | ---> Running in e339e4cb6838 333 | ---> 42a44756989e 334 | Successfully built 42a44756989e 335 |336 |
337 | Now let’s launch it. We use the -p
flag to publish the port publicly.
338 |
root@precise64:/home/vagrant/dockerfiles/tutorial1# docker run -p 11211 memcached
341 |
342 |
343 |
344 | Memcached is running :) Now which port should we use to access it from outside the container?
345 | As we asked for it with the EXPOSE
instruction, the container is exposing the 11211
port, and we used -p
publish it to our system. We can use the docker ps
command in order to get the port we should use from outside the container to access Memcached inside the container.
346 |
root@precise64:/home/vagrant/dockerfiles/tutorial1# docker ps
349 | ID IMAGE COMMAND CREATED STATUS PORTS NAMES
350 | 1aa7a504438c memcached:latest memcached 2 minutes ago Up 2 minutes 0.0.0.0:49153->11211/tcp blue_whale
351 |
352 |
353 | In this example the column PORTS
shows that we should use the port 49153
.
354 | NOTE: you could also use the docker port
or the docker inspect
commands to retrieve the right port number.
355 |
360 | We will here use python to play with Memcached, but you can use any language (Go, PHP, Ruby, …). This is a simple Python script to use your Memcached (Don’t forget to update the port number, you will probably not have the same). 361 |
362 |363 | # run this command if import memcache fails 364 | # pip install python-memcached 365 | 366 | import memcache 367 | 368 | ip = 'localhost' 369 | port = 49153 370 | 371 | mc = memcache.Client(["{0}:{1}".format(ip, port)], debug=0) 372 | mc.set("MDM", "Guillaume J. C.") 373 | value = mc.get("MDM") 374 | 375 | print value 376 |377 |
378 | Let’s run it! 379 |
380 |root@precise64:/home/vagrant/dockerfiles/tutorial2# python test.py
381 | Guillaume J. C.
382 |
383 | 384 | Congratulations! You just used your first containerized Memcached server :) 385 |
386 | 387 |436 | Your best friend Roberto Hashioka sent you a Dockerfile, but some parts were lost in the ocean. Can you find the missing parts? 437 |
438 |439 | # Redis 440 | # 441 | # VERSION 0.42 442 | 443 | # use the ubuntu base image provided by dotCloud 444 | ub 445 | 446 | MAINT Ro Ha roberto.hashioka@dotcloud.com 447 | 448 | # make sure the package repository is up to date 449 | echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 450 | apt-get update 451 | 452 | # install wget (required for redis installation) 453 | apt-get install -y wget 454 | 455 | # install make (required for redis installation) 456 | apt-get install -y make 457 | 458 | # install gcc (required for redis installation) 459 | RUN apt-get install -y 460 | 461 | # install apache2 462 | wget http://download.redis.io/redis-stable.tar.gz 463 | tar xvzf redis-stable.tar.gz 464 | cd redis-stable && make && make install 465 | 466 | # launch redis when starting the image 467 | ["redis-server"] 468 | 469 | # run as user dameon 470 | daemon 471 | 472 | # expose port 6379 473 | 6379 474 |475 | 476 | 479 | 480 | 481 | 482 |
485 | We will be posting Level 3 in the next few weeks. Follow us on twitter and/or enter your email address to be alerted!
486 | Follow @docker
487 |
488 |
In the meantime, check out this blog post by Michael Crosby that describes Dockerfile Best Practices.
497 | 498 | 499 |