diff --git a/cmd/web.go b/cmd/web.go
index 2ab680ecf3..ff4fcc376d 100644
--- a/cmd/web.go
+++ b/cmd/web.go
@@ -309,7 +309,7 @@ func runWeb(ctx *cli.Context) error {
 				return
 			}
 		})
-		m.Post("/issues/attachments", repo.UploadIssueAttachment)
+		m.Post("/attachments", repo.UploadAttachment)
 	}, ignSignIn)
 
 	m.Group("/:username", func() {
@@ -463,13 +463,11 @@ func runWeb(ctx *cli.Context) error {
 			m.Get("/:id/:action", repo.ChangeMilestonStatus)
 			m.Post("/delete", repo.DeleteMilestone)
 		}, reqRepoWriter, context.RepoRef())
-
 		m.Group("/releases", func() {
 			m.Get("/new", repo.NewRelease)
 			m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
 			m.Post("/delete", repo.DeleteRelease)
 		}, reqRepoWriter, context.RepoRef())
-
 		m.Group("/releases", func() {
 			m.Get("/edit/*", repo.EditRelease)
 			m.Post("/edit/*", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
diff --git a/conf/app.ini b/conf/app.ini
index f0a2b8ceed..ec4a3e3034 100644
--- a/conf/app.ini
+++ b/conf/app.ini
@@ -289,7 +289,7 @@ ENABLE = true
 ; Path for attachments. Defaults to `data/attachments`
 PATH = data/attachments
 ; One or more allowed types, e.g. image/jpeg|image/png
-ALLOWED_TYPES = image/jpeg|image/png
+ALLOWED_TYPES = image/jpeg|image/png|application/zip|application/gzip
 ; Max size of each file. Defaults to 32MB
 MAX_SIZE = 4
 ; Max number of files per upload. Defaults to 10
diff --git a/models/release.go b/models/release.go
index 67ef81e21c..113a0d68e4 100644
--- a/models/release.go
+++ b/models/release.go
@@ -38,6 +38,8 @@ type Release struct {
 	IsDraft          bool   `xorm:"NOT NULL DEFAULT false"`
 	IsPrerelease     bool
 
+	Attachments []*Attachment `xorm:"-"`
+
 	Created     time.Time `xorm:"-"`
 	CreatedUnix int64     `xorm:"INDEX"`
 }
@@ -155,8 +157,33 @@ func createTag(gitRepo *git.Repository, rel *Release) error {
 	return nil
 }
 
+func addReleaseAttachments(releaseID int64, attachmentUUIDs []string) (err error) {
+	// Check attachments
+	var attachments = make([]*Attachment,0)
+	for _, uuid := range attachmentUUIDs {
+		attach, err := getAttachmentByUUID(x, uuid)
+		if err != nil {
+			if IsErrAttachmentNotExist(err) {
+				continue
+			}
+			return fmt.Errorf("getAttachmentByUUID [%s]: %v", uuid, err)
+		}
+		attachments = append(attachments, attach)
+	}
+
+	for i := range attachments {
+		attachments[i].ReleaseID = releaseID
+		// No assign value could be 0, so ignore AllCols().
+		if _, err = x.Id(attachments[i].ID).Update(attachments[i]); err != nil {
+			return fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err)
+		}
+	}
+
+	return
+}
+
 // CreateRelease creates a new release of repository.
-func CreateRelease(gitRepo *git.Repository, rel *Release) error {
+func CreateRelease(gitRepo *git.Repository, rel *Release, attachmentUUIDs []string) error {
 	isExist, err := IsReleaseExist(rel.RepoID, rel.TagName)
 	if err != nil {
 		return err
@@ -168,7 +195,14 @@ func CreateRelease(gitRepo *git.Repository, rel *Release) error {
 		return err
 	}
 	rel.LowerTagName = strings.ToLower(rel.TagName)
+
 	_, err = x.InsertOne(rel)
+	if err != nil {
+		return err
+	}
+
+	err = addReleaseAttachments(rel.ID, attachmentUUIDs)
+
 	return err
 }
 
@@ -222,6 +256,64 @@ func GetReleasesByRepoIDAndNames(repoID int64, tagNames []string) (rels []*Relea
 	return rels, err
 }
 
+type releaseMetaSearch struct {
+	ID [] int64
+	Rel [] *Release
+}
+func (s releaseMetaSearch) Len() int {
+	return len(s.ID)
+}
+func (s releaseMetaSearch) Swap(i, j int) {
+	s.ID[i], s.ID[j] = s.ID[j], s.ID[i]
+	s.Rel[i], s.Rel[j] = s.Rel[j], s.Rel[i]
+}
+func (s releaseMetaSearch) Less(i, j int) bool {
+	return s.ID[i] < s.ID[j]
+}
+
+// GetReleaseAttachments retrieves the attachments for releases
+func GetReleaseAttachments(rels ... *Release) (err error){
+	if len(rels) == 0 {
+		return
+	}
+
+	// To keep this efficient as possible sort all releases by id, 
+	//    select attachments by release id,
+	//    then merge join them
+
+	// Sort
+	var sortedRels = releaseMetaSearch{ID: make([]int64, len(rels)), Rel: make([]*Release, len(rels))}
+	var attachments [] *Attachment
+	for index, element := range rels {
+		element.Attachments = []*Attachment{}
+		sortedRels.ID[index] = element.ID
+		sortedRels.Rel[index] = element
+	}
+	sort.Sort(sortedRels)
+
+	// Select attachments
+	err = x.
+		Asc("release_id").
+		In("release_id", sortedRels.ID).
+		Find(&attachments, Attachment{})
+
+	if err != nil {
+		return err
+	}
+
+	// merge join
+	var currentIndex = 0
+	for _, attachment := range attachments {
+		for sortedRels.ID[currentIndex] < attachment.ReleaseID {
+			currentIndex++
+		}
+		sortedRels.Rel[currentIndex].Attachments = append(sortedRels.Rel[currentIndex].Attachments, attachment)
+	}
+
+	return
+
+}
+
 type releaseSorter struct {
 	rels []*Release
 }
@@ -249,11 +341,17 @@ func SortReleases(rels []*Release) {
 }
 
 // UpdateRelease updates information of a release.
-func UpdateRelease(gitRepo *git.Repository, rel *Release) (err error) {
+func UpdateRelease(gitRepo *git.Repository, rel *Release, attachmentUUIDs []string) (err error) {
 	if err = createTag(gitRepo, rel); err != nil {
 		return err
 	}
 	_, err = x.Id(rel.ID).AllCols().Update(rel)
+	if err != nil {
+		return err
+	}
+
+	err = addReleaseAttachments(rel.ID, attachmentUUIDs)
+
 	return err
 }
 
diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go
index 8a200c0206..dda1f9d252 100644
--- a/modules/auth/repo_form.go
+++ b/modules/auth/repo_form.go
@@ -267,6 +267,7 @@ type NewReleaseForm struct {
 	Content    string
 	Draft      string
 	Prerelease bool
+	Files   []string
 }
 
 // Validate valideates the fields
@@ -280,6 +281,7 @@ type EditReleaseForm struct {
 	Content    string `form:"content"`
 	Draft      string `form:"draft"`
 	Prerelease bool   `form:"prerelease"`
+	Files   []string
 }
 
 // Validate valideates the fields
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index 523582f703..ab916c1b53 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -718,7 +718,7 @@ please consider changing to GITEA_CUSTOM`)
 	if !filepath.IsAbs(AttachmentPath) {
 		AttachmentPath = path.Join(workDir, AttachmentPath)
 	}
-	AttachmentAllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png"), "|", ",", -1)
+	AttachmentAllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png,application/zip,application/gzip"), "|", ",", -1)
 	AttachmentMaxSize = sec.Key("MAX_SIZE").MustInt64(4)
 	AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(5)
 	AttachmentEnabled = sec.Key("ENABLE").MustBool(true)
diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go
index d14f2f3cae..7dacb8018a 100644
--- a/routers/api/v1/repo/release.go
+++ b/routers/api/v1/repo/release.go
@@ -99,7 +99,7 @@ func CreateRelease(ctx *context.APIContext, form api.CreateReleaseOption) {
 		IsPrerelease: form.IsPrerelease,
 		CreatedUnix:  commit.Author.When.Unix(),
 	}
-	if err := models.CreateRelease(ctx.Repo.GitRepo, rel); err != nil {
+	if err := models.CreateRelease(ctx.Repo.GitRepo, rel, nil); err != nil {
 		if models.IsErrReleaseAlreadyExist(err) {
 			ctx.Status(409)
 		} else {
@@ -145,7 +145,7 @@ func EditRelease(ctx *context.APIContext, form api.EditReleaseOption) {
 	if form.IsPrerelease != nil {
 		rel.IsPrerelease = *form.IsPrerelease
 	}
-	if err := models.UpdateRelease(ctx.Repo.GitRepo, rel); err != nil {
+	if err := models.UpdateRelease(ctx.Repo.GitRepo, rel, nil); err != nil {
 		ctx.Error(500, "UpdateRelease", err)
 		return
 	}
diff --git a/routers/repo/issue.go b/routers/repo/issue.go
index a43a3d992b..f77b44f633 100644
--- a/routers/repo/issue.go
+++ b/routers/repo/issue.go
@@ -477,8 +477,8 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
 	ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index))
 }
 
-// UploadIssueAttachment response for uploading issue's attachment
-func UploadIssueAttachment(ctx *context.Context) {
+// UploadAttachment response for uploading issue's attachment
+func UploadAttachment(ctx *context.Context) {
 	if !setting.AttachmentEnabled {
 		ctx.Error(404, "attachment is not enabled")
 		return
diff --git a/routers/repo/release.go b/routers/repo/release.go
index 01ed3c37a1..3e0fc94e4e 100644
--- a/routers/repo/release.go
+++ b/routers/repo/release.go
@@ -15,6 +15,7 @@ import (
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/markdown"
+	"code.gitea.io/gitea/modules/setting"
 	"github.com/Unknwon/paginater"
 )
 
@@ -99,6 +100,12 @@ func Releases(ctx *context.Context) {
 		return
 	}
 
+	err = models.GetReleaseAttachments(releases...)
+	if err != nil {
+		ctx.Handle(500, "GetReleaseAttachments", err)
+		return
+	}
+
 	// Temproray cache commits count of used branches to speed up.
 	countCache := make(map[string]int64)
 	var cacheUsers = make(map[int64]*models.User)
@@ -162,6 +169,7 @@ func NewRelease(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
 	ctx.Data["PageIsReleaseList"] = true
 	ctx.Data["tag_target"] = ctx.Repo.Repository.DefaultBranch
+	renderAttachmentSettings(ctx);
 	ctx.HTML(200, tplReleaseNew)
 }
 
@@ -215,7 +223,12 @@ func NewReleasePost(ctx *context.Context, form auth.NewReleaseForm) {
 		CreatedUnix:  tagCreatedUnix,
 	}
 
-	if err = models.CreateRelease(ctx.Repo.GitRepo, rel); err != nil {
+	var attachmentUUIDs []string
+	if setting.AttachmentEnabled {
+		attachmentUUIDs = form.Files
+	}
+
+	if err = models.CreateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs); err != nil {
 		ctx.Data["Err_TagName"] = true
 		switch {
 		case models.IsErrReleaseAlreadyExist(err):
@@ -237,6 +250,7 @@ func EditRelease(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("repo.release.edit_release")
 	ctx.Data["PageIsReleaseList"] = true
 	ctx.Data["PageIsEditRelease"] = true
+	renderAttachmentSettings(ctx);
 
 	tagName := ctx.Params("*")
 	rel, err := models.GetRelease(ctx.Repo.Repository.ID, tagName)
@@ -286,11 +300,16 @@ func EditReleasePost(ctx *context.Context, form auth.EditReleaseForm) {
 		return
 	}
 
+	var attachmentUUIDs []string
+	if setting.AttachmentEnabled {
+		attachmentUUIDs = form.Files
+	}
+
 	rel.Title = form.Title
 	rel.Note = form.Content
 	rel.IsDraft = len(form.Draft) > 0
 	rel.IsPrerelease = form.Prerelease
-	if err = models.UpdateRelease(ctx.Repo.GitRepo, rel); err != nil {
+	if err = models.UpdateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs); err != nil {
 		ctx.Handle(500, "UpdateRelease", err)
 		return
 	}
diff --git a/templates/repo/issue/comment_tab.tmpl b/templates/repo/issue/comment_tab.tmpl
index b009060cd1..3af3d8a041 100644
--- a/templates/repo/issue/comment_tab.tmpl
+++ b/templates/repo/issue/comment_tab.tmpl
@@ -13,5 +13,5 @@
 </div>
 {{if .IsAttachmentEnabled}}
 	<div class="files"></div>
-	<div class="ui basic button dropzone" id="dropzone" data-upload-url="{{AppSubUrl}}/issues/attachments" data-accepts="{{.AttachmentAllowedTypes}}" data-max-file="{{.AttachmentMaxFiles}}" data-max-size="{{.AttachmentMaxSize}}" data-default-message="{{.i18n.Tr "dropzone.default_message"}}" data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}" data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}" data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}"></div>
+	<div class="ui basic button dropzone" id="dropzone" data-upload-url="{{AppSubUrl}}/attachments" data-accepts="{{.AttachmentAllowedTypes}}" data-max-file="{{.AttachmentMaxFiles}}" data-max-size="{{.AttachmentMaxSize}}" data-default-message="{{.i18n.Tr "dropzone.default_message"}}" data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}" data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}" data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}"></div>
 {{end}}
diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl
index 4879e04811..c18bc4884d 100644
--- a/templates/repo/release/list.tmpl
+++ b/templates/repo/release/list.tmpl
@@ -59,6 +59,15 @@
 									<li>
 										<a href="{{$.RepoLink}}/archive/{{.TagName}}.tar.gz"><i class="octicon octicon-file-zip"></i> {{$.i18n.Tr "repo.release.source_code"}} (TAR.GZ)</a>
 									</li>
+									{{if .Attachments}}
+									{{range .Attachments}}
+									<li>
+										<a target="_blank" rel="noopener" href="{{AppSubUrl}}/attachments/{{.UUID}}">
+											<span class="ui image octicon octicon-desktop-download" title='{{.Name}}'></span> {{.Name}}
+										</a>
+									</li>
+									{{end}}
+									{{end}}
 								</ul>
 							</div>
 						{{else}}
diff --git a/templates/repo/release/new.tmpl b/templates/repo/release/new.tmpl
index 3a9e6fadd8..5f82cf2f52 100644
--- a/templates/repo/release/new.tmpl
+++ b/templates/repo/release/new.tmpl
@@ -48,6 +48,10 @@
 					<label>{{.i18n.Tr "repo.release.content"}}</label>
 					<textarea name="content">{{.content}}</textarea>
 				</div>
+				{{if .IsAttachmentEnabled}}
+					<div class="files"></div>
+					<div class="ui basic button dropzone" id="dropzone" data-upload-url="{{AppSubUrl}}/attachments" data-accepts="{{.AttachmentAllowedTypes}}" data-max-file="{{.AttachmentMaxFiles}}" data-max-size="{{.AttachmentMaxSize}}" data-default-message="{{.i18n.Tr "dropzone.default_message"}}" data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}" data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}" data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}"></div>
+				{{end}}
 			</div>
 			<div class="ui container">
 				<div class="ui divider"></div>