diff --git a/package.json b/package.json
index a23ad57d..8520cb82 100644
--- a/package.json
+++ b/package.json
@@ -73,6 +73,7 @@
     "draft-js-export-markdown": "^0.2.0",
     "draft-js-import-markdown": "^0.1.6",
     "fuzzy": "^0.1.1",
+    "js-base64": "^2.1.9",
     "json-loader": "^0.5.4",
     "localforage": "^1.4.2",
     "lodash": "^4.13.1"
diff --git a/src/actions/entries.js b/src/actions/entries.js
index 329c7318..af0ff572 100644
--- a/src/actions/entries.js
+++ b/src/actions/entries.js
@@ -11,7 +11,7 @@ export const ENTRIES_REQUEST = 'ENTRIES_REQUEST';
 export const ENTRIES_SUCCESS = 'ENTRIES_SUCCESS';
 export const ENTRIES_FAILURE = 'ENTRIES_FAILURE';
 
-export const DRAFT_CREATE = 'DRAFT_CREATE';
+export const DRAFT_CREATE_FROM_ENTRY = 'DRAFT_CREATE_FROM_ENTRY';
 export const DRAFT_DISCARD = 'DRAFT_DISCARD';
 export const DRAFT_CHANGE = 'DRAFT_CHANGE';
 
@@ -104,9 +104,9 @@ function entryPersistFail(collection, entry, error) {
 /*
  * Exported simple Action Creators
  */
-export function createDraft(entry) {
+export function createDraftFromEntry(entry) {
   return {
-    type: DRAFT_CREATE,
+    type: DRAFT_CREATE_FROM_ENTRY,
     payload: entry
   };
 }
@@ -152,12 +152,12 @@ export function loadEntries(collection) {
   };
 }
 
-export function persist(collection, entry, mediaFiles) {
+export function persistEntry(collection, entry, mediaFiles) {
   return (dispatch, getState) => {
     const state = getState();
     const backend = currentBackend(state.config);
     dispatch(entryPersisting(collection, entry));
-    backend.persist(collection, entry, mediaFiles).then(
+    backend.persistEntry(collection, entry, mediaFiles).then(
       ({persistedEntry, persistedMediaFiles}) => {
         dispatch(entryPersisted(persistedEntry, persistedMediaFiles));
       },
diff --git a/src/actions/media.js b/src/actions/media.js
index a45e6f64..1e5bd81f 100644
--- a/src/actions/media.js
+++ b/src/actions/media.js
@@ -5,6 +5,6 @@ export function addMedia(mediaProxy) {
   return { type: ADD_MEDIA, payload: mediaProxy };
 }
 
-export function removeMedia(uri) {
-  return { type: REMOVE_MEDIA, payload: uri };
+export function removeMedia(path) {
+  return { type: REMOVE_MEDIA, payload: path };
 }
diff --git a/src/backends/backend.js b/src/backends/backend.js
index ee639f0c..02d44065 100644
--- a/src/backends/backend.js
+++ b/src/backends/backend.js
@@ -67,14 +67,21 @@ class Backend {
     };
   }
 
-  persist(collection, entryDraft) {
+  persistEntry(collection, entryDraft) {
     const entryData = entryDraft.getIn(['entry', 'data']).toObject();
     const entryObj = {
       path: entryDraft.getIn(['entry', 'path']),
       slug: entryDraft.getIn(['entry', 'slug']),
       raw: this.entryToRaw(collection, entryData)
     };
-    return this.implementation.persist(collection, entryObj, entryDraft.get('mediaFiles').toJS()).then(
+
+    const commitMessage = (entryDraft.getIn(['entry', 'newRecord']) ? 'Created ' : 'Updated ') +
+          collection.get('label') + ' “' +
+          entryDraft.getIn(['entry', 'data', 'title']) + '”';
+
+
+    return this.implementation.persistEntry(collection, entryObj, entryDraft.get('mediaFiles').toJS(), { commitMessage })
+    .then(
       (response) => ({
         persistedEntry: this.entryWithFormat(collection)(response.persistedEntry),
         persistedMediaFiles:response.persistedMediaFiles
diff --git a/src/backends/github/implementation.js b/src/backends/github/implementation.js
index d9bd08af..8d5bed35 100644
--- a/src/backends/github/implementation.js
+++ b/src/backends/github/implementation.js
@@ -1,5 +1,7 @@
 import LocalForage from 'localforage';
+import MediaProxy from '../../valueObjects/MediaProxy';
 import AuthenticationPage from './AuthenticationPage';
+import { Base64 } from 'js-base64';
 
 const API_ROOT = 'https://api.github.com';
 
@@ -40,6 +42,43 @@ class API {
     });
   }
 
+  persistFiles(collection, entry, mediaFiles, options) {
+    let filename, part, parts, subtree;
+    const fileTree = {};
+    const files = [];
+
+    mediaFiles.concat(entry).forEach((file) => {
+      if (file.uploaded) { return; }
+      files.push(this.uploadBlob(file));
+      parts = file.path.split('/').filter((part) => part);
+      filename = parts.pop();
+      subtree = fileTree;
+      while (part = parts.shift()) {
+        subtree[part] = subtree[part] || {};
+        subtree = subtree[part];
+      }
+      subtree[filename] = file;
+      file.file = true;
+    });
+
+    return Promise.all(files)
+      .then(() => this.getBranch())
+      .then((branchData) => {
+        return this.updateTree(branchData.commit.sha, '/', fileTree);
+      })
+      .then((changeTree) => {
+        return this.request(`${this.repoURL}/git/commits`, {
+          type: 'POST',
+          data: JSON.stringify({ message: options.message, tree: changeTree.sha, parents: [changeTree.parentSha] })
+        });
+      }).then((response) => {
+        return this.request(`${this.repoURL}/git/refs/heads/${this.branch}`, {
+          type: 'PATCH',
+          data: JSON.stringify({ sha: response.sha })
+        });
+      });
+  }
+
   requestHeaders(headers = {}) {
     return {
       Authorization: `token ${this.token}`,
@@ -68,6 +107,78 @@ class API {
       return response.text();
     });
   }
+
+  getBranch() {
+    return this.request(`${this.repoURL}/branches/${this.branch}`);
+  }
+
+  getTree(sha) {
+    return sha ? this.request(`${this.repoURL}/git/trees/${sha}`) : Promise.resolve({ tree: [] });
+  }
+
+  toBase64(str) {
+    return Promise.resolve(
+      Base64.encode(str)
+    );
+  }
+
+  uploadBlob(item) {
+    const content = item instanceof MediaProxy ? item.toBase64() : this.toBase64(item.raw);
+
+    return content.then((contentBase64) => {
+      return this.request(`${this.repoURL}/git/blobs`, {
+        method: 'POST',
+        body: JSON.stringify({
+          content: contentBase64,
+          encoding: 'base64'
+        })
+      }).then((response) => {
+        item.sha = response.sha;
+        item.uploaded = true;
+        return item;
+      });
+    });
+  }
+
+  updateTree(sha, path, fileTree) {
+    return this.getTree(sha)
+      .then((tree) => {
+        var obj, filename, fileOrDir;
+        var updates = [];
+        var added = {};
+
+        for (var i = 0, len = tree.tree.length; i < len; i++) {
+          obj = tree.tree[i];
+          if (fileOrDir = fileTree[obj.path]) {
+            added[obj.path] = true;
+            if (fileOrDir.file) {
+              updates.push({ path: obj.path, mode: obj.mode, type: obj.type, sha: fileOrDir.sha });
+            } else {
+              updates.push(this.updateTree(obj.sha, obj.path, fileOrDir));
+            }
+          }
+        }
+        for (filename in fileTree) {
+          fileOrDir = fileTree[filename];
+          if (added[filename]) { continue; }
+          updates.push(
+            fileOrDir.file ?
+              { path: filename, mode: '100644', type: 'blob', sha: fileOrDir.sha } :
+              this.updateTree(null, filename, fileOrDir)
+          );
+        }
+        return Promise.all(updates)
+          .then((updates) => {
+            return this.request(`${this.repoURL}/git/trees`, {
+              type: 'POST',
+              data: JSON.stringify({ base_tree: sha, tree: updates })
+            });
+          }).then((response) => {
+            return { path: path, mode: '040000', type: 'tree', sha: response.sha, parentSha: sha };
+          });
+      });
+  }
+
 }
 
 export default class GitHub {
@@ -115,4 +226,8 @@ export default class GitHub {
       response.entries.filter((entry) => entry.slug === slug)[0]
     ));
   }
+
+  persistEntry(collection, entry, mediaFiles = []) {
+    return this.api.persistFiles(collection, entry, mediaFiles);
+  }
 }
diff --git a/src/backends/test-repo/implementation.js b/src/backends/test-repo/implementation.js
index 2afc719b..a97210b6 100644
--- a/src/backends/test-repo/implementation.js
+++ b/src/backends/test-repo/implementation.js
@@ -48,10 +48,10 @@ export default class TestRepo {
     ));
   }
 
-  persist(collection, entry, mediaFiles = []) {
-    const folder = collection.get('folder');
+  persistEntry(collection, entry, mediaFiles = []) {
+    const folder = entry.path.substring(0, entry.path.lastIndexOf('/'));
     const fileName = entry.path.substring(entry.path.lastIndexOf('/') + 1);
     window.repoFiles[folder][fileName]['content'] = entry.raw;
-    return Promise.resolve({persistedEntry:entry, persistedMediaFiles:[]});
+    return Promise.resolve({ persistedEntry:entry, persistedMediaFiles:[] });
   }
 }
diff --git a/src/components/Widgets/ImageControl.js b/src/components/Widgets/ImageControl.js
index bd9757d7..db4e293c 100644
--- a/src/components/Widgets/ImageControl.js
+++ b/src/components/Widgets/ImageControl.js
@@ -53,7 +53,7 @@ export default class ImageControl extends React.Component {
     if (file) {
       const mediaProxy = new MediaProxy(file.name, file);
       this.props.onAddMedia(mediaProxy);
-      this.props.onChange(mediaProxy.uri);
+      this.props.onChange(mediaProxy.path);
     } else {
       this.props.onChange(null);
     }
@@ -63,7 +63,7 @@ export default class ImageControl extends React.Component {
   renderImageName() {
     if (!this.props.value) return null;
     if (this.value instanceof MediaProxy) {
-      return truncateMiddle(this.props.value.uri, MAX_DISPLAY_LENGTH);
+      return truncateMiddle(this.props.value.path, MAX_DISPLAY_LENGTH);
     } else {
       return truncateMiddle(this.props.value, MAX_DISPLAY_LENGTH);
     }
diff --git a/src/containers/EntryPage.js b/src/containers/EntryPage.js
index d03b744b..79784c0e 100644
--- a/src/containers/EntryPage.js
+++ b/src/containers/EntryPage.js
@@ -3,10 +3,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 import { connect } from 'react-redux';
 import {
   loadEntry,
-  createDraft,
+  createDraftFromEntry,
   discardDraft,
   changeDraft,
-  persist
+  persistEntry
 } from '../actions/entries';
 import { addMedia, removeMedia } from '../actions/media';
 import { selectEntry, getMedia } from '../reducers';
@@ -16,18 +16,18 @@ class EntryPage extends React.Component {
   constructor(props) {
     super(props);
     this.props.loadEntry(props.collection, props.slug);
-    this.handlePersist = this.handlePersist.bind(this);
+    this.handlePersistEntry = this.handlePersistEntry.bind(this);
   }
 
   componentDidMount() {
     if (this.props.entry) {
-      this.props.createDraft(this.props.entry);
+      this.props.createDraftFromEntry(this.props.entry);
     }
   }
 
   componentWillReceiveProps(nextProps) {
     if (this.props.entry !== nextProps.entry && !nextProps.entry.get('isFetching')) {
-      this.props.createDraft(nextProps.entry);
+      this.props.createDraftFromEntry(nextProps.entry);
     }
   }
 
@@ -35,8 +35,8 @@ class EntryPage extends React.Component {
     this.props.discardDraft();
   }
 
-  handlePersist() {
-    this.props.persist(this.props.collection, this.props.entryDraft);
+  handlePersistEntry() {
+    this.props.persistEntry(this.props.collection, this.props.entryDraft);
   }
 
   render() {
@@ -56,7 +56,7 @@ class EntryPage extends React.Component {
           onChange={changeDraft}
           onAddMedia={addMedia}
           onRemoveMedia={removeMedia}
-          onPersist={this.handlePersist}
+          onPersist={this.handlePersistEntry}
       />
     );
   }
@@ -67,12 +67,12 @@ EntryPage.propTypes = {
   boundGetMedia: PropTypes.func.isRequired,
   changeDraft: PropTypes.func.isRequired,
   collection: ImmutablePropTypes.map.isRequired,
-  createDraft: PropTypes.func.isRequired,
+  createDraftFromEntry: PropTypes.func.isRequired,
   discardDraft: PropTypes.func.isRequired,
   entry: ImmutablePropTypes.map.isRequired,
   entryDraft: ImmutablePropTypes.map.isRequired,
   loadEntry: PropTypes.func.isRequired,
-  persist: PropTypes.func.isRequired,
+  persistEntry: PropTypes.func.isRequired,
   removeMedia: PropTypes.func.isRequired,
   slug: PropTypes.string.isRequired,
 };
@@ -93,8 +93,8 @@ export default connect(
     addMedia,
     removeMedia,
     loadEntry,
-    createDraft,
+    createDraftFromEntry,
     discardDraft,
-    persist
+    persistEntry
   }
 )(EntryPage);
diff --git a/src/formats/yaml.js b/src/formats/yaml.js
index 944f3e9e..9fd73c07 100644
--- a/src/formats/yaml.js
+++ b/src/formats/yaml.js
@@ -19,7 +19,7 @@ const ImageType = new yaml.Type('image', {
   kind: 'scalar',
   instanceOf: MediaProxy,
   represent: function(value) {
-    return `${value.uri}`;
+    return `${value.path}`;
   },
   resolve: function(value) {
     if (value === null) return false;
diff --git a/src/reducers/entryDraft.js b/src/reducers/entryDraft.js
index 801f3ea0..8f23e8c9 100644
--- a/src/reducers/entryDraft.js
+++ b/src/reducers/entryDraft.js
@@ -1,12 +1,12 @@
 import { Map, List } from 'immutable';
-import { DRAFT_CREATE, DRAFT_DISCARD, DRAFT_CHANGE } from '../actions/entries';
+import { DRAFT_CREATE_FROM_ENTRY, DRAFT_DISCARD, DRAFT_CHANGE } from '../actions/entries';
 import { ADD_MEDIA, REMOVE_MEDIA } from '../actions/media';
 
 const initialState = Map({ entry: Map(), mediaFiles: List() });
 
 const entryDraft = (state = Map(), action) => {
   switch (action.type) {
-    case DRAFT_CREATE:
+    case DRAFT_CREATE_FROM_ENTRY:
       if (!action.payload) {
         // New entry
         return initialState;
@@ -14,6 +14,7 @@ const entryDraft = (state = Map(), action) => {
       // Existing Entry
       return state.withMutations((state) => {
         state.set('entry', action.payload);
+        state.setIn(['entry', 'newRecord'], false);
         state.set('mediaFiles', List());
       });
     case DRAFT_DISCARD:
@@ -22,9 +23,9 @@ const entryDraft = (state = Map(), action) => {
       return state.set('entry', action.payload);
 
     case ADD_MEDIA:
-      return state.update('mediaFiles', (list) => list.push(action.payload.uri));
+      return state.update('mediaFiles', (list) => list.push(action.payload.path));
     case REMOVE_MEDIA:
-      return state.update('mediaFiles', (list) => list.filterNot((uri) => uri === action.payload));
+      return state.update('mediaFiles', (list) => list.filterNot((path) => path === action.payload));
 
     default:
       return state;
diff --git a/src/reducers/index.js b/src/reducers/index.js
index 29a7cdf3..ecdac295 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -23,5 +23,5 @@ export const selectEntry = (state, collection, slug) =>
 export const selectEntries = (state, collection) =>
   fromEntries.selectEntries(state.entries, collection);
 
-export const getMedia = (state, uri) =>
-  fromMedias.getMedia(state.medias, uri);
+export const getMedia = (state, path) =>
+  fromMedias.getMedia(state.medias, path);
diff --git a/src/reducers/medias.js b/src/reducers/medias.js
index e80d24dc..0b502c22 100644
--- a/src/reducers/medias.js
+++ b/src/reducers/medias.js
@@ -6,12 +6,12 @@ import MediaProxy from '../valueObjects/MediaProxy';
 const medias = (state = Map(), action) => {
   switch (action.type) {
     case ADD_MEDIA:
-      return state.set(action.payload.uri, action.payload);
+      return state.set(action.payload.path, action.payload);
     case REMOVE_MEDIA:
       return state.delete(action.payload);
     case ENTRY_PERSIST_SUCCESS:
-      return state.map((media, uri) => {
-        if (action.payload.persistedMediaFiles.indexOf(uri) > -1) media.uploaded = true;
+      return state.map((media, path) => {
+        if (action.payload.persistedMediaFiles.indexOf(path) > -1) media.uploaded = true;
         return media;
       });
 
@@ -22,10 +22,10 @@ const medias = (state = Map(), action) => {
 
 export default medias;
 
-export const getMedia = (state, uri) => {
-  if (state.has(uri)) {
-    return state.get(uri);
+export const getMedia = (state, path) => {
+  if (state.has(path)) {
+    return state.get(path);
   } else {
-    return new MediaProxy(uri, null, true);
+    return new MediaProxy(path, null, true);
   }
 };
diff --git a/src/valueObjects/MediaProxy.js b/src/valueObjects/MediaProxy.js
index 7fd93ca0..d92e2993 100644
--- a/src/valueObjects/MediaProxy.js
+++ b/src/valueObjects/MediaProxy.js
@@ -7,8 +7,21 @@ export default function MediaProxy(value, file, uploaded = false) {
   this.value = value;
   this.file = file;
   this.uploaded = uploaded;
-  this.uri = config.media_folder && !uploaded ? config.media_folder + '/' + value : value;
-  this.toString = function() {
-    return this.uploaded ? this.uri : window.URL.createObjectURL(this.file, { oneTimeOnly: true });
-  };
+  this.sha = null;
+  this.path = config.media_folder && !uploaded ? config.media_folder + '/' + value : value;
 }
+
+MediaProxy.prototype.toString = function() {
+  return this.uploaded ? this.path : window.URL.createObjectURL(this.file, { oneTimeOnly: true });
+};
+
+MediaProxy.prototype.toBase64 = function() {
+  return new Promise((resolve, reject) => {
+    const fr = new FileReader();
+    fr.onload = (readerEvt) => {
+      const binaryString = readerEvt.target.result;
+      resolve(btoa(binaryString));
+    };
+    fr.readAsDataURL(this.file);
+  });
+};