diff --git a/snf-astakos-app/astakos/im/functions.py b/snf-astakos-app/astakos/im/functions.py
index ae78731bb73b2813795a6f4b3303adf5aa727f63..669e72e2105cf5d365156e3f9494aabe3d183f60 100644
--- a/snf-astakos-app/astakos/im/functions.py
+++ b/snf-astakos-app/astakos/im/functions.py
@@ -721,6 +721,76 @@ def enable_base_project(user):
     quotas.qh_sync_project(project)
 
 
+MODIFY_KEYS_MAIN = ["owner", "realname", "homepage", "description"]
+MODIFY_KEYS_EXTRA = ["end_date", "member_join_policy", "member_leave_policy",
+                     "limit_on_members_number", "private"]
+MODIFY_KEYS = MODIFY_KEYS_MAIN + MODIFY_KEYS_EXTRA
+
+
+def modifies_main_fields(request):
+    return set(request.keys()).intersection(MODIFY_KEYS_MAIN)
+
+
+def modify_project(project_id, request):
+    project = get_project_for_update(project_id)
+    if project.state not in Project.INITIALIZED_STATES:
+        m = _(astakos_messages.UNINITIALIZED_NO_MODIFY) % project.uuid
+        raise ProjectConflict(m)
+
+    if project.is_base:
+        main_fields = modifies_main_fields(request)
+        if main_fields:
+            m = (_(astakos_messages.BASE_NO_MODIFY_FIELDS)
+                 % ", ".join(map(str, main_fields)))
+            raise ProjectBadRequest(m)
+
+    new_name = request.get("realname")
+    if new_name is not None and project.is_alive:
+        check_conflicting_projects(project, new_name)
+        project.realname = new_name
+        project.name = new_name
+        project.save()
+
+    _modify_projects(Project.objects.filter(id=project.id), request)
+
+
+def modify_projects_in_bulk(flt, request):
+    main_fields = modifies_main_fields(request)
+    if main_fields:
+        raise ProjectBadRequest("Cannot modify field(s) '%s' in bulk" %
+                                ", ".join(map(str, main_fields)))
+
+    projects = Project.objects.initialized(flt).select_for_update()
+    _modify_projects(projects, request)
+
+
+def _modify_projects(projects, request):
+    upds = {}
+    for key in MODIFY_KEYS:
+        value = request.get(key)
+        if value is not None:
+            upds[key] = value
+    projects.update(**upds)
+
+    changed_resources = set()
+    pquotas = []
+    req_policies = request.get("resources", {})
+    req_policies = validate_resource_policies(req_policies, admin=True)
+    for project in projects:
+        for resource, m_capacity, p_capacity in req_policies:
+            changed_resources.add(resource)
+            pquotas.append(
+                ProjectResourceQuota(
+                    project=project,
+                    resource=resource,
+                    member_capacity=m_capacity,
+                    project_capacity=p_capacity))
+    ProjectResourceQuota.objects.\
+        filter(project__in=projects, resource__in=changed_resources).delete()
+    ProjectResourceQuota.objects.bulk_create(pquotas)
+    quotas.qh_sync_projects(projects)
+
+
 def submit_application(owner=None,
                        name=None,
                        project_id=None,
@@ -798,13 +868,15 @@ def submit_application(owner=None,
     return application
 
 
-def validate_resource_policies(policies):
+def validate_resource_policies(policies, admin=False):
     if not isinstance(policies, dict):
         raise ProjectBadRequest("Malformed resource policies")
 
     resource_names = policies.keys()
-    resources = Resource.objects.filter(name__in=resource_names,
-                                        api_visible=True)
+    resources = Resource.objects.filter(name__in=resource_names)
+    if not admin:
+        resources = resources.filter(api_visible=True)
+
     resource_d = {}
     for resource in resources:
         resource_d[resource.name] = resource
diff --git a/snf-astakos-app/astakos/im/management/commands/project-modify.py b/snf-astakos-app/astakos/im/management/commands/project-modify.py
new file mode 100644
index 0000000000000000000000000000000000000000..e636aff390527838f84350941ee443799f33fcaf
--- /dev/null
+++ b/snf-astakos-app/astakos/im/management/commands/project-modify.py
@@ -0,0 +1,169 @@
+# Copyright 2013 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from optparse import make_option
+
+from django.db.models import Q
+from django.core.management.base import BaseCommand, CommandError
+from django.db import transaction
+from synnefo.util import units
+from astakos.im import functions
+from astakos.im import models
+import astakos.api.projects as api
+import synnefo.util.date as date_util
+from snf_django.management import utils
+from astakos.im.management.commands import _common
+
+
+def make_policies(limits):
+    policies = {}
+    for (name, member_capacity, project_capacity) in limits:
+        try:
+            member_capacity = units.parse(member_capacity)
+            project_capacity = units.parse(project_capacity)
+        except units.ParseError:
+            m = "Please specify capacity as a decimal integer"
+            raise CommandError(m)
+        policies[name] = {"member_capacity": member_capacity,
+                          "project_capacity": project_capacity}
+    return policies
+
+Simple = type('Simple', (), {})
+
+
+class Param(object):
+    def __init__(self, key=Simple, mod=Simple, action=Simple, nargs=Simple,
+                 is_main=False, help=""):
+        self.key = key
+        self.mod = mod
+        self.action = action
+        self.nargs = nargs
+        self.is_main = is_main
+        self.help = help
+
+
+PARAMS = {
+    "name": Param(key="realname", help="Set project name"),
+    "owner": Param(mod=_common.get_accepted_user, help="Set project owner"),
+    "homepage": Param(help="Set project homepage"),
+    "description": Param(help="Set project description"),
+    "end_date": Param(mod=date_util.isoparse, is_main=True,
+                      help=("Set project end date in ISO format "
+                            "(e.g. 2014-01-01T00:00Z)")),
+    "join_policy": Param(key="member_join_policy", is_main=True,
+                         mod=(lambda x: api.MEMBERSHIP_POLICY[x]),
+                         help="Set join policy (auto, moderated, or closed)"),
+    "leave_policy": Param(key="member_leave_policy", is_main=True,
+                          mod=(lambda x: api.MEMBERSHIP_POLICY[x]),
+                          help=("Set leave policy "
+                                "(auto, moderated, or closed)")),
+    "max_members": Param(key="limit_on_members_number", mod=int, is_main=True,
+                         help="Set maximum members limit"),
+    "private": Param(mod=utils.parse_bool, is_main=True,
+                     help="Set project private"),
+    "limit": Param(key="resources", mod=make_policies, is_main=True,
+                   nargs=3, action="append",
+                   help=("Set resource limits: "
+                         "resource_name member_capacity project_capacity")),
+}
+
+
+def make_options():
+    options = []
+    for key, param in PARAMS.iteritems():
+        opt = "--" + key.replace('_', '-')
+        kwargs = {}
+        if param.action is not Simple:
+            kwargs["action"] = param.action
+        if param.nargs is not Simple:
+            kwargs["nargs"] = param.nargs
+        kwargs["help"] = param.help
+        options.append(make_option(opt, **kwargs))
+    return tuple(options)
+
+
+class Command(BaseCommand):
+    args = "<project id> (or --all-base-projects)"
+    help = "Modify an already initialized project"
+    option_list = BaseCommand.option_list + make_options() + (
+        make_option('--all-base-projects',
+                    action='store_true',
+                    default=False,
+                    help="Modify in bulk all initialized base projects"),
+        make_option('--exclude',
+                    help=("If `--all-base-projects' is given, exclude projects"
+                          " given as a list of uuids: uuid1,uuid2,uuid3")),
+        )
+
+    def check_args(self, args, all_base, exclude):
+        if all_base and args or not all_base and len(args) != 1:
+            m = "Please provide a project ID or --all-base-projects"
+            raise CommandError(m)
+        if not all_base and exclude:
+            m = ("Option --exclude is meaningful only combined with "
+                 " --all-base-projects.")
+            raise CommandError(m)
+
+    def mk_all_base_filter(self, all_base, exclude):
+        flt = Q(state__in=models.Project.INITIALIZED_STATES, is_base=True)
+        if exclude:
+            exclude = exclude.split(',')
+            flt &= ~Q(uuid__in=exclude)
+        return flt
+
+    @transaction.commit_on_success
+    def handle(self, *args, **options):
+        all_base = options["all_base_projects"]
+        exclude = options["exclude"]
+        self.check_args(args, all_base, exclude)
+
+        try:
+            changes = {}
+            for key, value in options.iteritems():
+                param = PARAMS.get(key)
+                if param is None or value is None:
+                    continue
+                if all_base and not param.is_main:
+                    m = "Cannot modify field '%s' in bulk" % key
+                    raise CommandError(m)
+                k = key if param.key is Simple else param.key
+                v = value if param.mod is Simple else param.mod(value)
+                changes[k] = v
+
+            if all_base:
+                flt = self.mk_all_base_filter(all_base, exclude)
+                functions.modify_projects_in_bulk(flt, changes)
+            else:
+                functions.modify_project(args[0], changes)
+        except BaseException as e:
+            raise CommandError(e)
diff --git a/snf-astakos-app/astakos/im/messages.py b/snf-astakos-app/astakos/im/messages.py
index a165cf060568fc4523b156586a227404e084f1c8..6df798bb1a42bd25e8b80aed91c449877e875f98 100644
--- a/snf-astakos-app/astakos/im/messages.py
+++ b/snf-astakos-app/astakos/im/messages.py
@@ -273,6 +273,8 @@ APPLICATION_CANNOT_CANCEL = "Cannot cancel application %s in state '%s'"
 APPLICATION_CANCELLED = "Your project application has been cancelled."
 REACHED_PENDING_APPLICATION_LIMIT = ("You have reached the maximum number "
                                      "of pending project applications: %s.")
+UNINITIALIZED_NO_MODIFY = "Cannot modify: project %s is not initialized."
+BASE_NO_MODIFY_FIELDS = "Cannot modify field(s) '%s' of base projects."
 
 PENDING_APPLICATION_LIMIT_ADD = \
     ("You are not allowed to create a new project "
diff --git a/snf-deploy/snfdeploy/components.py b/snf-deploy/snfdeploy/components.py
index d448b809274ced01cfaa7858d2f2eca25022f8c2..381394e5ac1a853c0757794b1d01316bc0837e3c 100644
--- a/snf-deploy/snfdeploy/components.py
+++ b/snf-deploy/snfdeploy/components.py
@@ -658,18 +658,18 @@ class Astakos(SynnefoComponent):
             ]
 
     def modify_all_quota(self):
-        cmd = "snf-manage user-modify -f --all --base-quota"
-        return [
-            "%s pithos.diskspace 40G" % cmd,
-            "%s astakos.pending_app 2" % cmd,
-            "%s cyclades.vm 4" % cmd,
-            "%s cyclades.disk 40G" % cmd,
-            "%s cyclades.total_ram 16G" % cmd,
-            "%s cyclades.ram 8G" % cmd,
-            "%s cyclades.total_cpu 32" % cmd,
-            "%s cyclades.cpu 16" % cmd,
-            "%s cyclades.network.private 4" % cmd,
-            "%s cyclades.floating_ip 4" % cmd,
+        cmd = "snf-manage project-modify --all-base-projects --limit"
+        return [
+            "%s pithos.diskspace 40G 40G" % cmd,
+            "%s astakos.pending_app 2 2" % cmd,
+            "%s cyclades.vm 4 4" % cmd,
+            "%s cyclades.disk 40G 40G" % cmd,
+            "%s cyclades.total_ram 16G 16G" % cmd,
+            "%s cyclades.ram 8G 8G" % cmd,
+            "%s cyclades.total_cpu 32 32" % cmd,
+            "%s cyclades.cpu 16 16" % cmd,
+            "%s cyclades.network.private 4 4" % cmd,
+            "%s cyclades.floating_ip 4 4" % cmd,
             ]
 
     def get_services(self):