You can use copy for the inner loop (which should be more efficient) and range for the outer loop (which results in nicer code).
Result:
duplicate := make([][]int, len(matrix))
for i := range matrix {
duplicate[i] = make([]int, len(matrix[i]))
copy(duplicate[i], matrix[i])
}
If your goal is efficiency, it may make sense to do more allocation up front. This doesn't lead to more readable code but will lead to more efficient code if you are doing this often. This code assumes you have at least one row and that all rows are of the same length. You will need to add tests for that.
n := len(matrix)
m := len(matrix[0])
duplicate := make([][]int, n)
data := make([]int, n*m)
for i := range matrix {
start := i*m
end := start + m
duplicate[i] = data[start:end:end]
copy(duplicate[i], matrix[i])
}
Depending on what you are doing, it may make sense to make a "matrix type" that is implemented using only a single slice. A slice of slices is not the most efficient data structure, even if it is simpler to work with.
Before deciding if you need to be efficient, make sure that you are spending a lot of time doing copying using profiling. Then, after you have determined this is in fact a hotspot, start running benchmarks. See https://golang.org/pkg/testing/#hdr-Benchmarks for details.