Repository: kubernetes/kompose
Pull request #1394 (2021-07-03 00:30:38)

support multiple containers in a pod
As is mentioned in #1012, ```kompose convert``` creates each service as its own pod with single container. 

```--multiple-container-mode``` enables creating multiple containers in a pod. This function is experimental.

In case that such many changes might destroy the behavior known as before, so a temporary argument ```--multiple-container-mode``` switches its behavior. If the argument wasn't specified, kompose converts your docker-compose.yml along with keeping the behavior before. If no problems are reported after released as stable, this development mode is better to be merged for readable codes.


## How it works
In your docker-compose.yml, add ```kompose.service.group={identifier grouped by pods}``` in ```labels``` to specify the containers in the same pod.
```yaml
version: "3"

services:
  nginx:
    image: nginx
    depends_on:
      - logs
    labels: 
      - kompose.service.group=sidecar
    environment:
      - HOGE=TEST
    volumes:
      - "/var/log/nginx"

  logs:
    image: busybox
    command: ["sh","-c","while true; do cat /var/log/nginx/access.log /var/log/nginx/error.log; sleep 30; done"]
    labels: 
      - kompose.service.group=sidecar
    env_file:
      - .env
    volumes:
      - "/var/log/nginx"
```

add ```--multiple-container-mode``` option in ```kompose convert```
```
kompose convert docker-compose.yml --multiple-container-mode
```

then, you'll get a yaml for deployment as follows

```.yaml
apiVersion: v1
items:
  - apiVersion: apps/v1
    kind: Deployment
    metadata:
      annotations:
        kompose.service.group: sidecar
      creationTimestamp: null
      labels:
        io.kompose.service: sidecar
      name: sidecar
    spec:
      replicas: 1
      selector:
        matchLabels:
          io.kompose.service: sidecar
      strategy:
        type: Recreate
      template:
        metadata:
          annotations:
            kompose.service.group: sidecar
          creationTimestamp: null
          labels:
            io.kompose.service: sidecar
        spec:
          containers:
            - env:
                - name: HOGE
                  value: TEST
              image: nginx
              name: nginx
              resources: {}
              volumeMounts:
                - mountPath: /var/log/nginx
                  name: nginx-claim0
            - args:
                - sh
                - -c
                - while true; do cat /var/log/nginx/access.log /var/log/nginx/error.log; sleep 30; done
              env:
                - name: HOGEHOGE
                  valueFrom:
                    configMapKeyRef:
                      key: HOGEHOGE
                      name: env
              image: busybox
              name: logs
              resources: {}
              volumeMounts:
                - mountPath: /var/log/nginx
                  name: logs-claim0
          restartPolicy: Always
          volumes:
            - name: nginx-claim0
              persistentVolumeClaim:
                claimName: nginx-claim0
            - name: logs-claim0
              persistentVolumeClaim:
                claimName: logs-claim0
    status: {}
  - apiVersion: v1
    data:
      HOGEHOGE: TEST
    kind: ConfigMap
    metadata:
      creationTimestamp: null
      labels:
        io.kompose.service: sidecar-env
      name: env
  - apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      creationTimestamp: null
      labels:
        io.kompose.service: logs-claim0
      name: logs-claim0
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 100Mi
    status: {}
kind: List
metadata: {}
```


k8s-ci-robot: Thanks for your pull request. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

:memo: **Please follow instructions at <https://git.k8s.io/community/CLA.md#the-contributor-license-agreement> to sign the CLA.**

It may take a couple minutes for the CLA signature to be fully registered; after that, please reply here with a new comment and we'll verify.  Thanks.

---

- If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address.  Check your existing CLA data and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).
- If you signed the CLA as a corporation, please sign in with your organization's credentials at <https://identity.linuxfoundation.org/projects/cncf> to be authorized.
- If you have done the above and are still having issues with the CLA being reported as unsigned, please log a ticket with the Linux Foundation Helpdesk: <https://support.linuxfoundation.org/>
- Should you encounter any issues with the Linux Foundation Helpdesk, send a message to the backup e-mail support address at: login-issues@jira.linuxfoundation.org

<!-- need_sender_cla -->

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
	
k8s-ci-robot: Welcome @tk42! <br><br>It looks like this is your first PR to <a href='https://github.com/kubernetes/kompose'>kubernetes/kompose</a> 🎉. Please refer to our [pull request process documentation](https://git.k8s.io/community/contributors/guide/pull-requests.md) to help your PR have a smooth ride to approval. <br><br>You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. [Here is the bot commands documentation](https://go.k8s.io/bot-commands). <br><br>You can also check if kubernetes/kompose has [its own contribution guidelines](https://github.com/kubernetes/kompose/tree/master/CONTRIBUTING.md). <br><br>You may want to refer to our [testing guide](https://git.k8s.io/community/contributors/devel/sig-testing/testing.md) if you run into trouble with your tests not passing. <br><br>If you are having difficulty getting your pull request seen, please follow the [recommended escalation practices](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#why-is-my-pull-request-not-getting-reviewed). Also, for tips and tricks in the contribution process you may want to read the [Kubernetes contributor cheat sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md). We want to make sure your contribution gets all the attention it needs! <br><br>Thank you, and welcome to Kubernetes. :smiley:
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/kompose/pull/1394#" title="Author self-approved">tk42</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **sebgoa** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @sebgoa` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fkompose).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes/kompose/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["sebgoa"]} -->
tk42: /check-cla
tk42: /check-cla
tk42: /help
hangyan: @tk42 You can follow the links and sign the CLA
tk42: @hangyan 
I signed up a CLA account and received its verification with email as well.
I found my commits contained wrong user.name in git config so I forced push those commits in correct user.name, but still failed. I have no clue.
tk42: /check-cla
hangyan: cla seems works now

Patch:
diff --git a/cmd/convert.go b/cmd/convert.go
index b4fc8bf0..21cc3d88 100644
--- a/cmd/convert.go
+++ b/cmd/convert.go
@@ -54,6 +54,10 @@ var (
 	// WithKomposeAnnotation decides if we will add metadata about this convert to resource's annotation.
 	// default is true.
 	WithKomposeAnnotation bool
+
+	// MultipleContainerMode which enables creating multi containers in a single pod is a developping function.
+	// default is false
+	MultipleContainerMode bool
 )
 
 var convertCmd = &cobra.Command{
@@ -95,6 +99,7 @@ var convertCmd = &cobra.Command{
 			IsDeploymentConfigFlag:      cmd.Flags().Lookup("deployment-config").Changed,
 			YAMLIndent:                  ConvertYAMLIndent,
 			WithKomposeAnnotation:       WithKomposeAnnotation,
+			MultipleContainerMode:       MultipleContainerMode,
 		}
 
 		// Validate before doing anything else. Use "bundle" if passed in.
@@ -124,6 +129,7 @@ func init() {
 	convertCmd.Flags().MarkHidden("daemon-set")
 	convertCmd.Flags().MarkHidden("replication-controller")
 	convertCmd.Flags().MarkHidden("deployment")
+	convertCmd.Flags().BoolVar(&MultipleContainerMode, "multiple-container-mode", false, "Create multiple containers grouped by 'kompose.service.group' label")
 
 	// OpenShift only
 	convertCmd.Flags().BoolVar(&ConvertDeploymentConfig, "deployment-config", true, "Generate an OpenShift deploymentconfig object")
diff --git a/docs/user-guide.md b/docs/user-guide.md
index 8e1b74df..62818ea1 100755
--- a/docs/user-guide.md
+++ b/docs/user-guide.md
@@ -158,6 +158,7 @@ The currently supported options are:
 | Key                  | Value                               |
 |----------------------|-------------------------------------|
 | kompose.service.type | nodeport / clusterip / loadbalancer / headless |
+| kompose.service.group | name to group the containers contained in a single pod |
 | kompose.service.expose | true / hostnames (separated by comma) |
 | kompose.service.nodeport.port | port value (string) | 
 | kompose.service.expose.tls-secret | secret name |
@@ -195,6 +196,28 @@ services:
       kompose.service.type: nodeport
 ```
 
+ - `kompose.service.group` defines the group of containers included in a single pod.
+
+For example:
+
+```yaml
+version: "3"
+
+services:
+  nginx:
+    image: nginx
+    depends_on:
+      - logs
+    labels: 
+      - kompose.service.group=sidecar
+
+  logs:
+    image: busybox
+    command: ["tail -f /var/log/nginx/access.log"]
+    labels: 
+      - kompose.service.group=sidecar
+```
+
 - `kompose.service.expose` defines if the service needs to be made accessible from outside the cluster or not. If the value is set to "true", the provider sets the endpoint automatically, and for any other value, the value is set as the hostname. If multiple ports are defined in a service, the first one is chosen to be the exposed.
     - For the Kubernetes provider, an ingress resource is created and it is assumed that an ingress controller has already been configured. If the value is set to a comma sepatated list, multiple hostnames are supported.Hostname with path is also supported.
     - For the OpenShift provider, a route is created.
diff --git a/go.mod b/go.mod
index 4297f935..5b64105f 100644
--- a/go.mod
+++ b/go.mod
@@ -17,6 +17,7 @@ replace github.com/containerd/containerd => github.com/containerd/containerd v1.
 replace golang.org/x/sys => golang.org/x/sys v0.0.0-20201029080932-201ba4db2418
 
 require (
+	github.com/deckarep/golang-set v1.7.1
 	github.com/docker/cli v0.0.0-20190711175710-5b38d82aa076
 	github.com/docker/go-connections v0.4.0
 	github.com/docker/libcompose v0.4.0
@@ -26,8 +27,11 @@ require (
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
 	github.com/imdario/mergo v0.3.10 // indirect
 	github.com/joho/godotenv v1.3.0
+	github.com/mattn/goveralls v0.0.9 // indirect
+	github.com/mitchellh/gox v1.0.1 // indirect
 	github.com/moby/sys/mount v0.1.1 // indirect
 	github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2 // indirect
+	github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 // indirect
 	github.com/novln/docker-parser v1.0.0
 	github.com/openshift/api v0.0.0-20200803131051-87466835fcc0
 	github.com/pkg/errors v0.9.1
@@ -35,7 +39,11 @@ require (
 	github.com/spf13/cast v1.3.1
 	github.com/spf13/cobra v1.0.0
 	github.com/spf13/viper v1.7.1
-	golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
+	golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
+	golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
+	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
+	golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect
+	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	gopkg.in/yaml.v2 v2.3.0
 	gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
 	gotest.tools/v3 v3.0.3 // indirect
diff --git a/go.sum b/go.sum
index b0f39fda..1e884a82 100644
--- a/go.sum
+++ b/go.sum
@@ -70,6 +70,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ=
+github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/docker/cli v20.10.0-beta1.0.20201029214301-1d20b15adc38+incompatible h1:r99CiNpN5pxrSuSH36suYxrbLxFOhBvQ0sEH6624MHs=
@@ -194,6 +196,8 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
 github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.0.0 h1:21MVWPKDphxa7ineQQTrCU5brh7OuVVAzGOCnnCPtE8=
+github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
@@ -244,6 +248,8 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
 github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/goveralls v0.0.9 h1:XmIwwrO9a9pqSW6IpI89BSCShzQxx0j/oKnnvELQNME=
+github.com/mattn/goveralls v0.0.9/go.mod h1:FRbM1PS8oVsOe9JtdzAAXM+DsvDMMHcM1C7drGJD8HY=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
@@ -251,6 +257,8 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
 github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI=
+github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4=
 github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@@ -268,6 +276,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 h1:8Q0qkMVC/MmWkpIdlvZgcv2o2jrlF6zqVOh7W5YHdMA=
+github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
 github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
 github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
 github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
@@ -385,6 +395,7 @@ github.com/xeipuuv/gojsonschema v1.2.1-0.20201027075954-b076d39a02e5 h1:ImnGIsrc
 github.com/xeipuuv/gojsonschema v1.2.1-0.20201027075954-b076d39a02e5/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
@@ -399,6 +410,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -414,12 +426,16 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -438,8 +454,9 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -447,12 +464,12 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM=
 golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -482,11 +499,16 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20200602230032-c00d67ef29d0 h1:6txNFSnY+tteYoO+hf01EpdYcYZiurdC9MDIrcUzEu4=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200602230032-c00d67ef29d0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go
index 9965e83e..50b19392 100644
--- a/pkg/kobject/kobject.go
+++ b/pkg/kobject/kobject.go
@@ -77,6 +77,8 @@ type ConvertOptions struct {
 	YAMLIndent int
 
 	WithKomposeAnnotation bool
+
+	MultipleContainerMode bool
 }
 
 // IsPodController indicate if the user want to use a controller
@@ -84,8 +86,11 @@ func (opt *ConvertOptions) IsPodController() bool {
 	return opt.IsDeploymentFlag || opt.IsDaemonSetFlag || opt.IsReplicationControllerFlag || opt.Controller != ""
 }
 
+type ServiceConfigGroup []ServiceConfig
+
 // ServiceConfig holds the basic struct of a container
 type ServiceConfig struct {
+	Name              string
 	ContainerName     string
 	Image             string              `compose:"image"`
 	Environment       []EnvVar            `compose:"environment"`
diff --git a/pkg/loader/compose/utils.go b/pkg/loader/compose/utils.go
index c67a7166..516b5be7 100644
--- a/pkg/loader/compose/utils.go
+++ b/pkg/loader/compose/utils.go
@@ -32,6 +32,8 @@ import (
 const (
 	// LabelServiceType defines the type of service to be created
 	LabelServiceType = "kompose.service.type"
+	// LabelServiceGroup defines the group of services in a single pod
+	LabelServiceGroup = "kompose.service.group"
 	// LabelNodePortPort defines the port value for NodePort service
 	LabelNodePortPort = "kompose.service.nodeport.port"
 	// LabelServiceExpose defines if the service needs to be made accessible from outside the cluster or not
@@ -151,7 +153,7 @@ func normalizeVolumes(svcName string) string {
 }
 
 func normalizeNetworkNames(netName string) (string, error) {
-	netval := strings.ToLower(netName)
+	netval := strings.ToLower(strings.Replace(netName, "_", "-", -1))
 	regString := ("[^A-Za-z0-9.-]+")
 	reg, err := regexp.Compile(regString)
 	if err != nil {
diff --git a/pkg/loader/compose/v1v2.go b/pkg/loader/compose/v1v2.go
index a682f464..9697c539 100644
--- a/pkg/loader/compose/v1v2.go
+++ b/pkg/loader/compose/v1v2.go
@@ -178,6 +178,7 @@ func libComposeToKomposeMapping(composeObject *project.Project) (kobject.Kompose
 	// all relevant information as well as avoid the unsupported keys as well.
 	for name, composeServiceConfig := range composeObject.ServiceConfigs.All() {
 		serviceConfig := kobject.ServiceConfig{}
+		serviceConfig.Name = name
 		serviceConfig.Image = composeServiceConfig.Image
 		serviceConfig.Build = composeServiceConfig.Build.Context
 		newName := normalizeContainerNames(composeServiceConfig.ContainerName)
@@ -256,7 +257,14 @@ func libComposeToKomposeMapping(composeObject *project.Project) (kobject.Kompose
 			if len(composeServiceConfig.Networks.Networks) > 0 {
 				for _, value := range composeServiceConfig.Networks.Networks {
 					if value.Name != "default" {
-						serviceConfig.Network = append(serviceConfig.Network, value.RealName)
+						nomalizedNetworkName, err := normalizeNetworkNames(value.RealName)
+						if err != nil {
+							return kobject.KomposeObject{}, errors.Wrap(err, "Error trying to normalize network names")
+						}
+						if nomalizedNetworkName != value.RealName {
+							log.Warnf("Network name in docker-compose has been changed from %q to %q", value.RealName, nomalizedNetworkName)
+						}
+						serviceConfig.Network = append(serviceConfig.Network, nomalizedNetworkName)
 					}
 				}
 			}
diff --git a/pkg/loader/compose/v3.go b/pkg/loader/compose/v3.go
index 3b3e5eb4..962cd569 100644
--- a/pkg/loader/compose/v3.go
+++ b/pkg/loader/compose/v3.go
@@ -364,6 +364,7 @@ func dockerComposeToKomposeMapping(composeObject *types.Config) (kobject.Kompose
 		// No need to modify before importation
 		name := composeServiceConfig.Name
 		serviceConfig := kobject.ServiceConfig{}
+		serviceConfig.Name = name
 		serviceConfig.Image = composeServiceConfig.Image
 		serviceConfig.WorkingDir = composeServiceConfig.WorkingDir
 		serviceConfig.Annotations = map[string]string(composeServiceConfig.Labels)
diff --git a/pkg/transformer/kubernetes/k8sutils.go b/pkg/transformer/kubernetes/k8sutils.go
index f3cfbd32..3798b028 100644
--- a/pkg/transformer/kubernetes/k8sutils.go
+++ b/pkg/transformer/kubernetes/k8sutils.go
@@ -33,6 +33,7 @@ import (
 
 	"github.com/joho/godotenv"
 	"github.com/kubernetes/kompose/pkg/kobject"
+	"github.com/kubernetes/kompose/pkg/loader/compose"
 	"github.com/kubernetes/kompose/pkg/transformer"
 	deployapi "github.com/openshift/api/apps/v1"
 	"github.com/pkg/errors"
@@ -440,11 +441,44 @@ func (k *Kubernetes) CreateHeadlessService(name string, service kobject.ServiceC
 
 	return svc
 }
+func (k *Kubernetes) UpdateKubernetesObjectsMultipleContainers(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, objects *[]runtime.Object, podSpec PodSpec) error {
+	// Configure annotations
+	annotations := transformer.ConfigAnnotations(service)
+
+	// fillTemplate fills the pod template with the value calculated from config
+	fillTemplate := func(template *api.PodTemplateSpec) error {
+		template.ObjectMeta.Labels = transformer.ConfigLabelsWithNetwork(name, service.Network)
+		template.Spec = podSpec.Get()
+		return nil
+	}
+
+	// fillObjectMeta fills the metadata with the value calculated from config
+	fillObjectMeta := func(meta *metav1.ObjectMeta) {
+		meta.Annotations = annotations
+	}
+
+	// update supported controller
+	for _, obj := range *objects {
+		err := k.UpdateController(obj, fillTemplate, fillObjectMeta)
+		if err != nil {
+			return errors.Wrap(err, "k.UpdateController failed")
+		}
+		if len(service.Volumes) > 0 {
+			switch objType := obj.(type) {
+			case *appsv1.Deployment:
+				objType.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType
+			case *deployapi.DeploymentConfig:
+				objType.Spec.Strategy.Type = deployapi.DeploymentStrategyTypeRecreate
+			}
+		}
+	}
+	return nil
+}
 
 // UpdateKubernetesObjects loads configurations to k8s objects
 func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, objects *[]runtime.Object) error {
 	// Configure the environment variables.
-	envs, err := k.ConfigEnvs(name, service, opt)
+	envs, err := ConfigEnvs(name, service, opt)
 	if err != nil {
 		return errors.Wrap(err, "Unable to load env variables")
 	}
@@ -479,10 +513,10 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
 	}
 
 	// Configure the container ports.
-	ports := k.ConfigPorts(name, service)
+	ports := ConfigPorts(name, service)
 
 	// Configure capabilities
-	capabilities := k.ConfigCapabilities(service)
+	capabilities := ConfigCapabilities(service)
 
 	// Configure annotations
 	annotations := transformer.ConfigAnnotations(service)
@@ -662,6 +696,20 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
 	return nil
 }
 
+// KomposeObjectToServiceConfigGroupMapping returns the service config group by name
+func KomposeObjectToServiceConfigGroupMapping(komposeObject kobject.KomposeObject) map[string]kobject.ServiceConfigGroup {
+	serviceConfigGroup := make(map[string]kobject.ServiceConfigGroup)
+	for name, service := range komposeObject.ServiceConfigs {
+		if groupID, ok := service.Labels[compose.LabelServiceGroup]; ok {
+			service.Name = name
+			serviceConfigGroup[groupID] = append(serviceConfigGroup[groupID], service)
+		} else {
+			serviceConfigGroup[name] = append(serviceConfigGroup[name], service)
+		}
+	}
+	return serviceConfigGroup
+}
+
 // TranslatePodResource config pod resources
 func TranslatePodResource(service *kobject.ServiceConfig, template *api.PodTemplateSpec) {
 	// Configure the resource limits
diff --git a/pkg/transformer/kubernetes/kubernetes.go b/pkg/transformer/kubernetes/kubernetes.go
index faf7224b..10912ffd 100644
--- a/pkg/transformer/kubernetes/kubernetes.go
+++ b/pkg/transformer/kubernetes/kubernetes.go
@@ -536,7 +536,7 @@ func (k *Kubernetes) CreatePVC(name string, mode string, size string, selectorVa
 }
 
 // ConfigPorts configures the container ports.
-func (k *Kubernetes) ConfigPorts(name string, service kobject.ServiceConfig) []api.ContainerPort {
+func ConfigPorts(name string, service kobject.ServiceConfig) []api.ContainerPort {
 	ports := []api.ContainerPort{}
 	exist := map[string]bool{}
 	for _, port := range service.Port {
@@ -641,7 +641,7 @@ func (k *Kubernetes) ConfigServicePorts(name string, service kobject.ServiceConf
 }
 
 //ConfigCapabilities configure POSIX capabilities that can be added or removed to a container
-func (k *Kubernetes) ConfigCapabilities(service kobject.ServiceConfig) *api.Capabilities {
+func ConfigCapabilities(service kobject.ServiceConfig) *api.Capabilities {
 	capsAdd := []api.Capability{}
 	capsDrop := []api.Capability{}
 	for _, capAdd := range service.CapAdd {
@@ -947,7 +947,7 @@ func (k *Kubernetes) ConfigPVCVolumeSource(name string, readonly bool) *api.Volu
 }
 
 // ConfigEnvs configures the environment variables.
-func (k *Kubernetes) ConfigEnvs(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) ([]api.EnvVar, error) {
+func ConfigEnvs(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) ([]api.EnvVar, error) {
 	envs := transformer.EnvSort{}
 
 	keysFromEnvFile := make(map[string]bool)
@@ -1132,98 +1132,249 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
 		}
 	}
 
-	sortedKeys := SortedKeys(komposeObject)
-	for _, name := range sortedKeys {
-		service := komposeObject.ServiceConfigs[name]
-		var objects []runtime.Object
-
-		service.WithKomposeAnnotation = opt.WithKomposeAnnotation
-
-		// Must build the images before conversion (got to add service.Image in case 'image' key isn't provided
-		// Check that --build is set to true
-		// Check to see if there is an InputFile (required!) before we build the container
-		// Check that there's actually a Build key
-		// Lastly, we must have an Image name to continue
-		if opt.Build == "local" && opt.InputFiles != nil && service.Build != "" {
-			// If there's no "image" key, use the name of the container that's built
-			if service.Image == "" {
-				service.Image = name
-			}
+	if opt.MultipleContainerMode {
+		komposeObjectToServiceConfigGroupMapping := KomposeObjectToServiceConfigGroupMapping(komposeObject)
+		for name, group := range komposeObjectToServiceConfigGroupMapping {
+			service := komposeObject.ServiceConfigs[name]
+			var objects []runtime.Object
+
+			service.WithKomposeAnnotation = opt.WithKomposeAnnotation
+
+			// Must build the images before conversion (got to add service.Image in case 'image' key isn't provided
+			// Check that --build is set to true
+			// Check to see if there is an InputFile (required!) before we build the container
+			// Check that there's actually a Build key
+			// Lastly, we must have an Image name to continue
+			if opt.Build == "local" && opt.InputFiles != nil && service.Build != "" {
+				// If there's no "image" key, use the name of the container that's built
+				if service.Image == "" {
+					service.Image = name
+				}
 
-			if service.Image == "" {
-				return nil, fmt.Errorf("image key required within build parameters in order to build and push service '%s'", name)
-			}
+				if service.Image == "" {
+					return nil, fmt.Errorf("image key required within build parameters in order to build and push service '%s'", name)
+				}
 
-			log.Infof("Build key detected. Attempting to build image '%s'", service.Image)
+				log.Infof("Build key detected. Attempting to build image '%s'", service.Image)
 
-			// Build the image!
-			err := transformer.BuildDockerImage(service, name)
-			if err != nil {
-				return nil, errors.Wrapf(err, "Unable to build Docker image for service %v", name)
+				// Build the image!
+				err := transformer.BuildDockerImage(service, name)
+				if err != nil {
+					return nil, errors.Wrapf(err, "Unable to build Docker image for service %v", name)
+				}
+
+				// Push the built image to the repo!
+				if opt.PushImage {
+					log.Infof("Push image enabled. Attempting to push image '%s'", service.Image)
+					err = transformer.PushDockerImage(service, name)
+					if err != nil {
+						return nil, errors.Wrapf(err, "Unable to push Docker image for service %v", name)
+					}
+				}
 			}
 
-			// Push the built image to the repo!
-			if opt.PushImage {
-				log.Infof("Push image enabled. Attempting to push image '%s'", service.Image)
-				err = transformer.PushDockerImage(service, name)
+			podSpec := PodSpec{}
+
+			// added a container
+			for _, service := range group {
+				podSpec.Append(AddContainer(service, opt))
+
+				// Generate pod only and nothing more
+				if (service.Restart == "no" || service.Restart == "on-failure") && !opt.IsPodController() {
+					log.Infof("Create kubernetes pod instead of pod controller due to restart policy: %s", service.Restart)
+					pod := k.InitPod(name, service)
+					objects = append(objects, pod)
+				} else {
+					objects = k.CreateKubernetesObjects(name, service, opt)
+				}
+
+				if k.PortsExist(service) {
+					if service.ServiceType == "LoadBalancer" {
+						svcs := k.CreateLBService(name, service, objects)
+						for _, svc := range svcs {
+							objects = append(objects, svc)
+						}
+						if len(svcs) > 1 {
+							log.Warningf("Create multiple service to avoid using mixed protocol in the same service when it's loadbalander type")
+						}
+					} else {
+						svc := k.CreateService(name, service, objects)
+						objects = append(objects, svc)
+						if service.ExposeService != "" {
+							objects = append(objects, k.initIngress(name, service, svc.Spec.Ports[0].Port))
+						}
+					}
+				} else {
+					if service.ServiceType == "Headless" {
+						svc := k.CreateHeadlessService(name, service, objects)
+						objects = append(objects, svc)
+					} else {
+						log.Warnf("Service %q won't be created because 'ports' is not specified", name)
+					}
+				}
+
+				// Configure the container volumes.
+				volumesMount, volumes, pvc, cms, err := k.ConfigVolumes(name, service)
 				if err != nil {
-					return nil, errors.Wrapf(err, "Unable to push Docker image for service %v", name)
+					return nil, errors.Wrap(err, "k.ConfigVolumes failed")
+				}
+				podSpec.Append(
+					SetVolumeMounts(volumesMount),
+					SetVolumes(volumes),
+				)
+
+				// Configure Tmpfs
+				if len(service.TmpFs) > 0 {
+					TmpVolumesMount, TmpVolumes := k.ConfigTmpfs(name, service)
+
+					volumes = append(volumes, TmpVolumes...)
+
+					volumesMount = append(volumesMount, TmpVolumesMount...)
+				}
+
+				if pvc != nil {
+					// Looping on the slice pvc instead of `*objects = append(*objects, pvc...)`
+					// because the type of objects and pvc is different, but when doing append
+					// one element at a time it gets converted to runtime.Object for objects slice
+					for _, p := range pvc {
+						objects = append(objects, p)
+					}
+				}
+
+				if cms != nil {
+					for _, c := range cms {
+						objects = append(objects, c)
+					}
+				}
+
+				podSpec.Append(
+					SetPorts(name, service),
+					ImagePullPolicy(name, service),
+					RestartPolicy(name, service),
+					SecurityContext(name, service),
+					LivenessProbe(service),
+					ReadinessProbe(service),
+					HostName(service),
+					DomainName(service),
+					ResourcesLimits(service),
+					ResourcesRequests(service),
+					TerminationGracePeriodSeconds(name, service),
+				)
+
+				err = k.UpdateKubernetesObjectsMultipleContainers(name, service, opt, &objects, podSpec)
+				if err != nil {
+					return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
 				}
 			}
-		}
 
-		// Generate pod only and nothing more
-		if (service.Restart == "no" || service.Restart == "on-failure") && !opt.IsPodController() {
-			log.Infof("Create kubernetes pod instead of pod controller due to restart policy: %s", service.Restart)
-			pod := k.InitPod(name, service)
-			objects = append(objects, pod)
-		} else {
-			objects = k.CreateKubernetesObjects(name, service, opt)
+			if len(service.Network) > 0 {
+				for _, net := range service.Network {
+					log.Infof("Network %s is detected at Source, shall be converted to equivalent NetworkPolicy at Destination", net)
+					np, err := k.CreateNetworkPolicy(name, net)
+
+					if err != nil {
+						return nil, errors.Wrapf(err, "Unable to create Network Policy for network %v for service %v", net, name)
+					}
+					objects = append(objects, np)
+				}
+			}
+
+			allobjects = append(allobjects, objects...)
 		}
+	} else {
+		sortedKeys := SortedKeys(komposeObject)
+		for _, name := range sortedKeys {
+			service := komposeObject.ServiceConfigs[name]
+			var objects []runtime.Object
+
+			service.WithKomposeAnnotation = opt.WithKomposeAnnotation
+
+			// Must build the images before conversion (got to add service.Image in case 'image' key isn't provided
+			// Check that --build is set to true
+			// Check to see if there is an InputFile (required!) before we build the container
+			// Check that there's actually a Build key
+			// Lastly, we must have an Image name to continue
+			if opt.Build == "local" && opt.InputFiles != nil && service.Build != "" {
+				// If there's no "image" key, use the name of the container that's built
+				if service.Image == "" {
+					service.Image = name
+				}
 
-		if k.PortsExist(service) {
-			if service.ServiceType == "LoadBalancer" {
-				svcs := k.CreateLBService(name, service, objects)
-				for _, svc := range svcs {
-					objects = append(objects, svc)
+				if service.Image == "" {
+					return nil, fmt.Errorf("image key required within build parameters in order to build and push service '%s'", name)
 				}
-				if len(svcs) > 1 {
-					log.Warningf("Create multiple service to avoid using mixed protocol in the same service when it's loadbalander type")
+
+				log.Infof("Build key detected. Attempting to build image '%s'", service.Image)
+
+				// Build the image!
+				err := transformer.BuildDockerImage(service, name)
+				if err != nil {
+					return nil, errors.Wrapf(err, "Unable to build Docker image for service %v", name)
 				}
-			} else {
-				svc := k.CreateService(name, service, objects)
-				objects = append(objects, svc)
-				if service.ExposeService != "" {
-					objects = append(objects, k.initIngress(name, service, svc.Spec.Ports[0].Port))
+
+				// Push the built image to the repo!
+				if opt.PushImage {
+					log.Infof("Push image enabled. Attempting to push image '%s'", service.Image)
+					err = transformer.PushDockerImage(service, name)
+					if err != nil {
+						return nil, errors.Wrapf(err, "Unable to push Docker image for service %v", name)
+					}
 				}
 			}
-		} else {
-			if service.ServiceType == "Headless" {
-				svc := k.CreateHeadlessService(name, service, objects)
-				objects = append(objects, svc)
+
+			// Generate pod only and nothing more
+			if (service.Restart == "no" || service.Restart == "on-failure") && !opt.IsPodController() {
+				log.Infof("Create kubernetes pod instead of pod controller due to restart policy: %s", service.Restart)
+				pod := k.InitPod(name, service)
+				objects = append(objects, pod)
 			} else {
-				log.Warnf("Service %q won't be created because 'ports' is not specified", name)
+				objects = k.CreateKubernetesObjects(name, service, opt)
 			}
-		}
 
-		err := k.UpdateKubernetesObjects(name, service, opt, &objects)
-		if err != nil {
-			return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
-		}
+			if k.PortsExist(service) {
+				if service.ServiceType == "LoadBalancer" {
+					svcs := k.CreateLBService(name, service, objects)
+					for _, svc := range svcs {
+						objects = append(objects, svc)
+					}
+					if len(svcs) > 1 {
+						log.Warningf("Create multiple service to avoid using mixed protocol in the same service when it's loadbalander type")
+					}
+				} else {
+					svc := k.CreateService(name, service, objects)
+					objects = append(objects, svc)
+					if service.ExposeService != "" {
+						objects = append(objects, k.initIngress(name, service, svc.Spec.Ports[0].Port))
+					}
+				}
+			} else {
+				if service.ServiceType == "Headless" {
+					svc := k.CreateHeadlessService(name, service, objects)
+					objects = append(objects, svc)
+				} else {
+					log.Warnf("Service %q won't be created because 'ports' is not specified", name)
+				}
+			}
 
-		if len(service.Network) > 0 {
-			for _, net := range service.Network {
-				log.Infof("Network %s is detected at Source, shall be converted to equivalent NetworkPolicy at Destination", net)
-				np, err := k.CreateNetworkPolicy(name, net)
+			err := k.UpdateKubernetesObjects(name, service, opt, &objects)
+			if err != nil {
+				return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
+			}
 
-				if err != nil {
-					return nil, errors.Wrapf(err, "Unable to create Network Policy for network %v for service %v", net, name)
+			if len(service.Network) > 0 {
+				for _, net := range service.Network {
+					log.Infof("Network %s is detected at Source, shall be converted to equivalent NetworkPolicy at Destination", net)
+					np, err := k.CreateNetworkPolicy(name, net)
+
+					if err != nil {
+						return nil, errors.Wrapf(err, "Unable to create Network Policy for network %v for service %v", net, name)
+					}
+					objects = append(objects, np)
 				}
-				objects = append(objects, np)
 			}
-		}
 
-		allobjects = append(allobjects, objects...)
+			allobjects = append(allobjects, objects...)
+		}
 	}
 
 	// sort all object so Services are first
diff --git a/pkg/transformer/kubernetes/podspec.go b/pkg/transformer/kubernetes/podspec.go
new file mode 100644
index 00000000..51238ed9
--- /dev/null
+++ b/pkg/transformer/kubernetes/podspec.go
@@ -0,0 +1,352 @@
+package kubernetes
+
+import (
+	"reflect"
+	"strconv"
+
+	mapset "github.com/deckarep/golang-set"
+	"github.com/kubernetes/kompose/pkg/kobject"
+	"github.com/pkg/errors"
+	log "github.com/sirupsen/logrus"
+	api "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
+	"k8s.io/apimachinery/pkg/util/intstr"
+)
+
+type PodSpec struct {
+	api.PodSpec
+}
+
+type PodSpecOption func(*PodSpec)
+
+func AddContainer(service kobject.ServiceConfig, opt kobject.ConvertOptions) PodSpecOption {
+	return func(podSpec *PodSpec) {
+		name := service.Name
+		image := service.Image
+
+		if image == "" {
+			image = name
+		}
+
+		// do not override in openshift case?
+		if len(service.ContainerName) > 0 {
+			name = FormatContainerName(service.ContainerName)
+		}
+
+		envs, err := ConfigEnvs(name, service, opt)
+		if err != nil {
+			panic("Unable to load env variables")
+		}
+
+		podSpec.Containers = append(podSpec.Containers, api.Container{
+			Name:       name,
+			Image:      image,
+			Env:        envs,
+			Command:    service.Command,
+			Args:       service.Args,
+			WorkingDir: service.WorkingDir,
+			Stdin:      service.Stdin,
+			TTY:        service.Tty,
+		})
+		podSpec.NodeSelector = service.Placement
+	}
+}
+
+func ImagePullSecrets(pullSecret string) PodSpecOption {
+	return func(podSpec *PodSpec) {
+		podSpec.ImagePullSecrets = append(podSpec.ImagePullSecrets,
+			api.LocalObjectReference{
+				Name: pullSecret,
+			},
+		)
+	}
+}
+
+func TerminationGracePeriodSeconds(name string, service kobject.ServiceConfig) PodSpecOption {
+	return func(podSpec *PodSpec) {
+		var err error
+		if service.StopGracePeriod != "" {
+			podSpec.TerminationGracePeriodSeconds, err = DurationStrToSecondsInt(service.StopGracePeriod)
+			if err != nil {
+				log.Warningf("Failed to parse duration \"%v\" for service \"%v\"", service.StopGracePeriod, name)
+			}
+		}
+	}
+}
+
+// Configure the resource limits
+func ResourcesLimits(service kobject.ServiceConfig) PodSpecOption {
+	return func(podSpec *PodSpec) {
+		if service.MemLimit != 0 || service.CPULimit != 0 {
+			resourceLimit := api.ResourceList{}
+
+			if service.MemLimit != 0 {
+				resourceLimit[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemLimit), "RandomStringForFormat")
+			}
+
+			if service.CPULimit != 0 {
+				resourceLimit[api.ResourceCPU] = *resource.NewMilliQuantity(service.CPULimit, resource.DecimalSI)
+			}
+
+			for i := range podSpec.Containers {
+				podSpec.Containers[i].Resources.Limits = resourceLimit
+			}
+		}
+	}
+}
+
+// Configure the resource requests
+func ResourcesRequests(service kobject.ServiceConfig) PodSpecOption {
+	return func(podSpec *PodSpec) {
+		if service.MemReservation != 0 || service.CPUReservation != 0 {
+			resourceRequests := api.ResourceList{}
+
+			if service.MemReservation != 0 {
+				resourceRequests[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemReservation), "RandomStringForFormat")
+			}
+
+			if service.CPUReservation != 0 {
+				resourceRequests[api.ResourceCPU] = *resource.NewMilliQuantity(service.CPUReservation, resource.DecimalSI)
+			}
+
+			for i := range podSpec.Containers {
+				podSpec.Containers[i].Resources.Requests = resourceRequests
+			}
+		}
+	}
+}
+
+// Configure SecurityContext
+func SecurityContext(name string, service kobject.ServiceConfig) PodSpecOption {
+	return func(podSpec *PodSpec) {
+		// Configure resource reservations
+		podSecurityContext := &api.PodSecurityContext{}
+
+		//set pid namespace mode
+		if service.Pid != "" {
+			if service.Pid == "host" {
+				// podSecurityContext.HostPID = true
+			} else {
+				log.Warningf("Ignoring PID key for service \"%v\". Invalid value \"%v\".", name, service.Pid)
+			}
+		}
+
+		//set supplementalGroups
+		if service.GroupAdd != nil {
+			podSecurityContext.SupplementalGroups = service.GroupAdd
+		}
+
+		// Setup security context
+		securityContext := &api.SecurityContext{}
+		if service.Privileged {
+			securityContext.Privileged = &service.Privileged
+		}
+		if service.User != "" {
+			uid, err := strconv.ParseInt(service.User, 10, 64)
+			if err != nil {
+				log.Warn("Ignoring user directive. User to be specified as a UID (numeric).")
+			} else {
+				securityContext.RunAsUser = &uid
+			}
+		}
+
+		// Configure capabilities
+		capabilities := ConfigCapabilities(service)
+
+		//set capabilities if it is not empty
+		if len(capabilities.Add) > 0 || len(capabilities.Drop) > 0 {
+			securityContext.Capabilities = capabilities
+		}
+
+		// update template only if securityContext is not empty
+		if *securityContext != (api.SecurityContext{}) {
+			podSpec.Containers[0].SecurityContext = securityContext
+		}
+		if !reflect.DeepEqual(*podSecurityContext, api.PodSecurityContext{}) {
+			podSpec.SecurityContext = podSecurityContext
+		}
+	}
+}
+
+func SetVolumeNames(volumes []api.Volume) mapset.Set {
+	set := mapset.NewSet()
+	for _, volume := range volumes {
+		set.Add(volume.Name)
+	}
+	return set
+}
+
+func SetVolumes(volumes []api.Volume) PodSpecOption {
+	return func(podSpec *PodSpec) {
+		volumesSet := SetVolumeNames(volumes)
+		containerVolumesSet := SetVolumeNames(podSpec.Volumes)
+		for diffVolumeName := range volumesSet.Difference(containerVolumesSet).Iter() {
+			for _, volume := range volumes {
+				if volume.Name == diffVolumeName {
+					podSpec.Volumes = append(podSpec.Volumes, volume)
+					break
+				}
+			}
+		}
+	}
+}
+
+func SetVolumeMountPaths(volumesMount []api.VolumeMount) mapset.Set {
+	set := mapset.NewSet()
+	for _, volumeMount := range volumesMount {
+		set.Add(volumeMount.MountPath)
+	}
+	return set
+}
+
+func SetVolumeMounts(volumesMount []api.VolumeMount) PodSpecOption {
+	return func(podSpec *PodSpec) {
+		volumesMountSet := SetVolumeMountPaths(volumesMount)
+		for i := range podSpec.Containers {
+			containerVolumeMountsSet := SetVolumeMountPaths(podSpec.Containers[i].VolumeMounts)
+			for diffVolumeMountPath := range volumesMountSet.Difference(containerVolumeMountsSet).Iter() {
+				for _, volumeMount := range volumesMount {
+					if volumeMount.MountPath == diffVolumeMountPath {
+						podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, volumeMount)
+						break
+					}
+				}
+			}
+		}
+	}
+}
+
+// Configure ports
+func SetPorts(name string, service kobject.ServiceConfig) PodSpecOption {
+	return func(podSpec *PodSpec) {
+		// Configure the container ports.
+		ports := ConfigPorts(name, service)
+
+		for i := range podSpec.Containers {
+			podSpec.Containers[i].Ports = ports
+		}
+	}
+}
+
+// Configure the image pull policy
+func ImagePullPolicy(name string, service kobject.ServiceConfig) PodSpecOption {
+	return func(podSpec *PodSpec) {
+		if policy, err := GetImagePullPolicy(name, service.ImagePullPolicy); err != nil {
+			panic(err)
+		} else {
+			for i := range podSpec.Containers {
+				podSpec.Containers[i].ImagePullPolicy = policy
+			}
+		}
+	}
+}
+
+// Configure the container restart policy.
+func RestartPolicy(name string, service kobject.ServiceConfig) PodSpecOption {
+	return func(podSpec *PodSpec) {
+		if restart, err := GetRestartPolicy(name, service.Restart); err != nil {
+			panic(err)
+		} else {
+			podSpec.RestartPolicy = restart
+		}
+	}
+}
+
+func HostName(service kobject.ServiceConfig) PodSpecOption {
+	return func(podSpec *PodSpec) {
+		// Configure hostname/domain_name settings
+		if service.HostName != "" {
+			podSpec.Hostname = service.HostName
+		}
+	}
+}
+
+func DomainName(service kobject.ServiceConfig) PodSpecOption {
+	return func(podSpec *PodSpec) {
+		if service.DomainName != "" {
+			podSpec.Subdomain = service.DomainName
+		}
+	}
+}
+
+func LivenessProbe(service kobject.ServiceConfig) PodSpecOption {
+	return func(podSpec *PodSpec) {
+		// Configure the HealthCheck
+		// We check to see if it's blank
+		if !reflect.DeepEqual(service.HealthChecks.Liveness, kobject.HealthCheck{}) {
+			probe := api.Probe{}
+
+			if len(service.HealthChecks.Liveness.Test) > 0 {
+				probe.Handler = api.Handler{
+					Exec: &api.ExecAction{
+						Command: service.HealthChecks.Liveness.Test,
+					},
+				}
+			} else if !reflect.ValueOf(service.HealthChecks.Liveness.HTTPPath).IsZero() &&
+				!reflect.ValueOf(service.HealthChecks.Liveness.HTTPPort).IsZero() {
+				probe.Handler = api.Handler{
+					HTTPGet: &api.HTTPGetAction{
+						Path: service.HealthChecks.Liveness.HTTPPath,
+						Port: intstr.FromInt(int(service.HealthChecks.Liveness.HTTPPort)),
+					},
+				}
+			} else {
+				panic(errors.New("Health check must contain a command"))
+			}
+
+			probe.TimeoutSeconds = service.HealthChecks.Liveness.Timeout
+			probe.PeriodSeconds = service.HealthChecks.Liveness.Interval
+			probe.FailureThreshold = service.HealthChecks.Liveness.Retries
+
+			// See issue: https://github.com/docker/cli/issues/116
+			// StartPeriod has been added to docker/cli however, it is not yet added
+			// to compose. Once the feature has been implemented, this will automatically work
+			probe.InitialDelaySeconds = service.HealthChecks.Liveness.StartPeriod
+
+			for i := range podSpec.Containers {
+				podSpec.Containers[i].LivenessProbe = &probe
+			}
+		}
+	}
+}
+
+func ReadinessProbe(service kobject.ServiceConfig) PodSpecOption {
+	return func(podSpec *PodSpec) {
+		if !reflect.DeepEqual(service.HealthChecks.Readiness, kobject.HealthCheck{}) {
+			probeHealthCheckReadiness := api.Probe{}
+			if len(service.HealthChecks.Readiness.Test) > 0 {
+				probeHealthCheckReadiness.Handler = api.Handler{
+					Exec: &api.ExecAction{
+						Command: service.HealthChecks.Readiness.Test,
+					},
+				}
+			} else {
+				panic(errors.New("Health check must contain a command"))
+			}
+
+			probeHealthCheckReadiness.TimeoutSeconds = service.HealthChecks.Readiness.Timeout
+			probeHealthCheckReadiness.PeriodSeconds = service.HealthChecks.Readiness.Interval
+			probeHealthCheckReadiness.FailureThreshold = service.HealthChecks.Readiness.Retries
+
+			// See issue: https://github.com/docker/cli/issues/116
+			// StartPeriod has been added to docker/cli however, it is not yet added
+			// to compose. Once the feature has been implemented, this will automatically work
+			probeHealthCheckReadiness.InitialDelaySeconds = service.HealthChecks.Readiness.StartPeriod
+
+			for i := range podSpec.Containers {
+				podSpec.Containers[i].ReadinessProbe = &probeHealthCheckReadiness
+			}
+		}
+	}
+}
+
+func (podSpec *PodSpec) Append(ops ...PodSpecOption) *PodSpec {
+	for _, option := range ops {
+		option(podSpec)
+	}
+	return podSpec
+}
+
+func (podSpec *PodSpec) Get() api.PodSpec {
+	return podSpec.PodSpec
+}


Test patch:
diff --git a/pkg/loader/compose/compose_test.go b/pkg/loader/compose/compose_test.go
index 7b556705..a3fecd1b 100644
--- a/pkg/loader/compose/compose_test.go
+++ b/pkg/loader/compose/compose_test.go
@@ -453,7 +453,7 @@ func TestNormalizeNetworkNames(t *testing.T) {
 		composeNetworkName    string
 		normalizedNetworkName string
 	}{
-		{"foo_bar", "foobar"},
+		{"foo_bar", "foo-bar"},
 		{"foo", "foo"},
 		{"FOO", "foo"},
 		{"foo.bar", "foo.bar"},
diff --git a/pkg/transformer/kubernetes/kubernetes_test.go b/pkg/transformer/kubernetes/kubernetes_test.go
index d0b509f2..7f077556 100644
--- a/pkg/transformer/kubernetes/kubernetes_test.go
+++ b/pkg/transformer/kubernetes/kubernetes_test.go
@@ -24,6 +24,7 @@ import (
 	"testing"
 
 	"github.com/kubernetes/kompose/pkg/kobject"
+	"github.com/kubernetes/kompose/pkg/loader/compose"
 	"github.com/kubernetes/kompose/pkg/transformer"
 	deployapi "github.com/openshift/api/apps/v1"
 	"github.com/pkg/errors"
@@ -535,12 +536,106 @@ func TestConfigCapabilities(t *testing.T) {
 		"ConfigCapsNoAddDrop":   {kobject.ServiceConfig{CapAdd: nil, CapDrop: nil}, api.Capabilities{Add: []api.Capability{}, Drop: []api.Capability{}}},
 	}
 
-	k := Kubernetes{}
 	for name, test := range testCases {
 		t.Log("Test case:", name)
-		result := k.ConfigCapabilities(test.service)
+		result := ConfigCapabilities(test.service)
 		if !reflect.DeepEqual(result.Add, test.result.Add) || !reflect.DeepEqual(result.Drop, test.result.Drop) {
 			t.Errorf("Not expected result for ConfigCapabilities")
 		}
 	}
 }
+
+func TestMultipleContainersInPod(t *testing.T) {
+	groupName := "pod_group"
+	containerName := ""
+
+	createConfig := func(name string, containerName *string) kobject.ServiceConfig {
+		config := newServiceConfig()
+		config.Labels = map[string]string{compose.LabelServiceGroup: groupName}
+		config.Name = name
+		if containerName != nil {
+			config.ContainerName = *containerName
+		}
+		config.Volumes = []kobject.Volumes{
+			{
+				VolumeName: "mountVolume",
+				MountPath:  "/data",
+			},
+		}
+		return config
+	}
+
+	testCases := map[string]struct {
+		komposeObject   kobject.KomposeObject
+		opt             kobject.ConvertOptions
+		expectedNumObjs int
+		expectedNames   []string
+	}{
+		"Converted multiple containers": {
+			kobject.KomposeObject{
+				ServiceConfigs: map[string]kobject.ServiceConfig{
+					"app1": createConfig("app1", &containerName),
+					"app2": createConfig("app2", &containerName),
+				},
+			}, kobject.ConvertOptions{MultipleContainerMode: true}, 2, []string{"app1", "app2"}},
+		"Converted multiple containers to Deployments (D)": {
+			kobject.KomposeObject{
+				ServiceConfigs: map[string]kobject.ServiceConfig{
+					"app1": createConfig("app1", &containerName),
+					"app2": createConfig("app2", &containerName),
+				},
+			}, kobject.ConvertOptions{MultipleContainerMode: true, CreateD: true}, 3, []string{"app1", "app2"}},
+		"Converted multiple containers (ContainerName are nil) to Deployments (D)": {
+			kobject.KomposeObject{
+				ServiceConfigs: map[string]kobject.ServiceConfig{
+					"app1": createConfig("app1", nil),
+					"app2": createConfig("app2", nil),
+				},
+			}, kobject.ConvertOptions{MultipleContainerMode: true, CreateD: true}, 3, []string{"name", "name"}},
+		// TODO: add more tests
+	}
+
+	for name, test := range testCases {
+		t.Log("Test case:", name)
+		k := Kubernetes{}
+		// Run Transform
+		objs, err := k.Transform(test.komposeObject, test.opt)
+		if err != nil {
+			t.Error(errors.Wrap(err, "k.Transform failed"))
+		}
+		if len(objs) != test.expectedNumObjs {
+			t.Errorf("Expected %d objects returned, got %d", test.expectedNumObjs, len(objs))
+		}
+
+		// Check results
+		for _, obj := range objs {
+			if svc, ok := obj.(*api.Service); ok {
+				if svc.Name != groupName {
+					t.Errorf("Expected %v returned, got %v", groupName, svc.Name)
+				}
+			}
+			if deployment, ok := obj.(*appsv1.Deployment); ok {
+				if deployment.Name != groupName {
+					t.Errorf("Expected %v returned, got %v", groupName, deployment.Name)
+				}
+				if len(deployment.Spec.Template.Spec.Containers) != 2 {
+					t.Errorf("Expected %d returned, got %d", 2, len(deployment.Spec.Template.Spec.Containers))
+				}
+				nameSet := make(map[string]api.Container)
+				for _, container := range deployment.Spec.Template.Spec.Containers {
+					nameSet[container.Name] = container
+				}
+				if container, ok := nameSet[test.expectedNames[0]]; !ok {
+					t.Errorf("Expected %v returned, got %v", test.expectedNames[0], container.Name)
+				} else if len(container.VolumeMounts) != 1 {
+					t.Errorf("Expected %v returned, got %v", 1, len(container.VolumeMounts))
+				}
+				if container, ok := nameSet[test.expectedNames[1]]; !ok {
+					t.Errorf("Expected %v returned, got %v", test.expectedNames[1], container.Name)
+				} else if len(container.VolumeMounts) != 1 {
+					t.Errorf("Expected %v returned, got %v", 1, len(container.VolumeMounts))
+				}
+			}
+		}
+	}
+}